diff --git a/hook.c b/hook.c old mode 100644 new mode 100755 diff --git a/include/hook.h b/include/hook.h old mode 100644 new mode 100755 diff --git a/include/uc_priv.h b/include/uc_priv.h index e9ebb23f..a8d601be 100755 --- a/include/uc_priv.h +++ b/include/uc_priv.h @@ -16,9 +16,8 @@ QTAILQ_HEAD(CPUTailQ, CPUState); typedef struct MemoryBlock { - MemoryRegion *region; //inclusive + MemoryRegion *region; //inclusive begin uint64_t end; //exclusive - uint32_t perms; } MemoryBlock; typedef struct ModuleEntry { @@ -184,6 +183,6 @@ struct uc_struct { #include "qemu_macro.h" // check if this address is mapped in (via uc_mem_map()) -bool memory_mapping(struct uc_struct* uc, uint64_t address); +MemoryRegion *memory_mapping(struct uc_struct* uc, uint64_t address); #endif diff --git a/include/unicorn/unicorn.h b/include/unicorn/unicorn.h index 975581cc..862382a8 100755 --- a/include/unicorn/unicorn.h +++ b/include/unicorn/unicorn.h @@ -116,6 +116,7 @@ typedef enum uc_err { UC_ERR_HOOK, // Invalid hook type: uc_hook_add() UC_ERR_INSN_INVALID, // Quit emulation due to invalid instruction: uc_emu_start() UC_ERR_MAP, // Invalid memory mapping: uc_mem_map() + UC_ERR_MEM_WRITE_RO, // Quit emulation due to invalid memory WRITE: uc_emu_start() } uc_err; @@ -147,6 +148,7 @@ typedef enum uc_mem_type { UC_MEM_READ = 16, // Memory is read from UC_MEM_WRITE, // Memory is written to UC_MEM_READ_WRITE, // Memory is accessed (either READ or WRITE) + UC_MEM_WRITE_RO, // Read only memory is written to } uc_mem_type; // All type of hooks for uc_hook_add() API. diff --git a/qemu/include/exec/memory.h b/qemu/include/exec/memory.h index 81081405..7604e8ae 100755 --- a/qemu/include/exec/memory.h +++ b/qemu/include/exec/memory.h @@ -170,6 +170,7 @@ struct MemoryRegion { MemoryRegionIoeventfd *ioeventfds; NotifierList iommu_notify; struct uc_struct *uc; + uint32_t perms; //all perms, partially redundant with readonly }; /** diff --git a/qemu/memory.c b/qemu/memory.c index 8c5fd883..a1d668da 100755 --- a/qemu/memory.c +++ b/qemu/memory.c @@ -1160,6 +1160,7 @@ void memory_region_init_ram(struct uc_struct *uc, MemoryRegion *mr, if (!(perms & UC_PROT_WRITE)) { mr->readonly = true; } + mr->perms = perms; mr->terminates = true; mr->destructor = memory_region_destructor_ram; mr->ram_addr = qemu_ram_alloc(size, mr, errp); diff --git a/qemu/softmmu_template.h b/qemu/softmmu_template.h index a48ee8d5..86bca884 100755 --- a/qemu/softmmu_template.h +++ b/qemu/softmmu_template.h @@ -460,31 +460,53 @@ void helper_le_st_name(CPUArchState *env, target_ulong addr, DATA_TYPE val, target_ulong tlb_addr = env->tlb_table[mmu_idx][index].addr_write; uintptr_t haddr; + struct uc_struct *uc = env->uc; + MemoryRegion *mr = memory_mapping(uc, addr); + // Unicorn: callback on memory write - if (env->uc->hook_mem_write) { - struct hook_struct *trace = hook_find((uch)env->uc, UC_MEM_WRITE, addr); + if (uc->hook_mem_write) { + struct hook_struct *trace = hook_find((uch)uc, UC_MEM_WRITE, addr); if (trace) { - ((uc_cb_hookmem_t)trace->callback)((uch)env->uc, UC_MEM_WRITE, + ((uc_cb_hookmem_t)trace->callback)((uch)uc, UC_MEM_WRITE, (uint64_t)addr, (int)DATA_SIZE, (int64_t)val, trace->user_data); } } // Unicorn: callback on invalid memory - if (env->uc->hook_mem_idx && !memory_mapping(env->uc, addr)) { - if (!((uc_cb_eventmem_t)env->uc->hook_callbacks[env->uc->hook_mem_idx].callback)( - (uch)env->uc, UC_MEM_WRITE, addr, DATA_SIZE, (int64_t)val, - env->uc->hook_callbacks[env->uc->hook_mem_idx].user_data)) { + if (uc->hook_mem_idx && mr == NULL) { + if (!((uc_cb_eventmem_t)uc->hook_callbacks[uc->hook_mem_idx].callback)( + (uch)uc, UC_MEM_WRITE, addr, DATA_SIZE, (int64_t)val, + uc->hook_callbacks[uc->hook_mem_idx].user_data)) { // save error & quit env->invalid_addr = addr; env->invalid_error = UC_ERR_MEM_WRITE; // printf("***** Invalid memory write at " TARGET_FMT_lx "\n", addr); - cpu_exit(env->uc->current_cpu); + cpu_exit(uc->current_cpu); return; } else { env->invalid_error = UC_ERR_OK; } } + // Unicorn: callback on read only memory + if (mr != NULL && !(mr->perms & UC_PROT_WRITE)) { //read only memory + bool result = false; + if (uc->hook_mem_idx) { + result = ((uc_cb_eventmem_t)uc->hook_callbacks[uc->hook_mem_idx].callback)( + (uch)uc, UC_MEM_WRITE_RO, addr, DATA_SIZE, (int64_t)val, + uc->hook_callbacks[uc->hook_mem_idx].user_data); + } + if (result) { + env->invalid_error = UC_ERR_OK; + } + else { + env->invalid_addr = addr; + env->invalid_error = UC_ERR_MEM_WRITE_RO; + // printf("***** Invalid memory write (ro) at " TARGET_FMT_lx "\n", addr); + cpu_exit(uc->current_cpu); + return; + } + } /* Adjust the given return address. */ retaddr -= GETPC_ADJ; @@ -546,6 +568,8 @@ void helper_le_st_name(CPUArchState *env, target_ulong addr, DATA_TYPE val, Undo that for the recursion. */ glue(helper_ret_stb, MMUSUFFIX)(env, addr + i, val8, mmu_idx, retaddr + GETPC_ADJ); + if (env->invalid_error != UC_ERR_OK) + break; } return; } @@ -574,31 +598,54 @@ void helper_be_st_name(CPUArchState *env, target_ulong addr, DATA_TYPE val, target_ulong tlb_addr = env->tlb_table[mmu_idx][index].addr_write; uintptr_t haddr; + struct uc_struct *uc = env->uc; + MemoryRegion *mr = memory_mapping(uc, addr); + // Unicorn: callback on memory write - if (env->uc->hook_mem_write) { - struct hook_struct *trace = hook_find((uch)env->uc, UC_MEM_WRITE, addr); + if (uc->hook_mem_write) { + struct hook_struct *trace = hook_find((uch)uc, UC_MEM_WRITE, addr); if (trace) { - ((uc_cb_hookmem_t)trace->callback)((uch)env->uc, UC_MEM_WRITE, + ((uc_cb_hookmem_t)trace->callback)((uch)uc, UC_MEM_WRITE, (uint64_t)addr, (int)DATA_SIZE, (int64_t)val, trace->user_data); } } // Unicorn: callback on invalid memory - if (env->uc->hook_mem_idx && !memory_mapping(env->uc, addr)) { - if (!((uc_cb_eventmem_t)env->uc->hook_callbacks[env->uc->hook_mem_idx].callback)( - (uch)env->uc, UC_MEM_WRITE, addr, DATA_SIZE, (int64_t)val, - env->uc->hook_callbacks[env->uc->hook_mem_idx].user_data)) { + if (uc->hook_mem_idx && mr == NULL) { + if (!((uc_cb_eventmem_t)uc->hook_callbacks[uc->hook_mem_idx].callback)( + (uch)uc, UC_MEM_WRITE, addr, DATA_SIZE, (int64_t)val, + uc->hook_callbacks[uc->hook_mem_idx].user_data)) { // save error & quit env->invalid_addr = addr; env->invalid_error = UC_ERR_MEM_WRITE; // printf("***** Invalid memory write at " TARGET_FMT_lx "\n", addr); - cpu_exit(env->uc->current_cpu); + cpu_exit(uc->current_cpu); return; } else { env->invalid_error = UC_ERR_OK; } } + // Unicorn: callback on read only memory + if (mr != NULL && !(mr->perms & UC_PROT_WRITE)) { //read only memory + bool result = false; + if (uc->hook_mem_idx) { + result = ((uc_cb_eventmem_t)uc->hook_callbacks[uc->hook_mem_idx].callback)( + (uch)uc, UC_MEM_WRITE_RO, addr, DATA_SIZE, (int64_t)val, + uc->hook_callbacks[uc->hook_mem_idx].user_data); + } + if (result) { + env->invalid_error = UC_ERR_OK; + } + else { + env->invalid_addr = addr; + env->invalid_error = UC_ERR_MEM_WRITE_RO; + // printf("***** Invalid memory write (ro) at " TARGET_FMT_lx "\n", addr); + cpu_exit(uc->current_cpu); + return; + } + } + /* Adjust the given return address. */ retaddr -= GETPC_ADJ; @@ -659,6 +706,8 @@ void helper_be_st_name(CPUArchState *env, target_ulong addr, DATA_TYPE val, Undo that for the recursion. */ glue(helper_ret_stb, MMUSUFFIX)(env, addr + i, val8, mmu_idx, retaddr + GETPC_ADJ); + if (env->invalid_error != UC_ERR_OK) + break; } return; } diff --git a/regress/ro_mem_test.c b/regress/ro_mem_test.c index de7a8356..bacd5b3a 100755 --- a/regress/ro_mem_test.c +++ b/regress/ro_mem_test.c @@ -4,15 +4,22 @@ #include -const uint8_t PROGRAM[] = "\xeb\x08\x58\xc7\x00\x78\x56\x34\x12\x90\xe8\xf3\xff\xff\xff\x41\x41\x41\x41"; +const uint8_t PROGRAM[] = + "\xeb\x1a\x58\x83\xc0\x04\x83\xe0\xfc\x83\xc0\x01\xc7\x00\x78\x56" + "\x34\x12\x83\xc0\x07\xc7\x00\x21\x43\x65\x87\x90\xe8\xe1\xff\xff" + "\xff" "xxxxAAAAxxxBBBB"; +// total size: 33 bytes /* -bits 32 - jmp short bottom top: pop eax - mov dword [eax], 0x12345678 + add eax, 4 + and eax, 0xfffffffc + add eax, 1 ; unaligned + mov dword [eax], 0x12345678 ; try to write into code section + add eax, 7 ; aligned + mov dword [eax], 0x87654321 ; try to write into code section nop bottom: call top @@ -47,11 +54,15 @@ static bool hook_mem_invalid(uch handle, uc_mem_type type, upper = (esp + 0xfff) & ~0xfff; printf(">>> Stack appears to be missing at 0x%"PRIx64 ", allocating now\n", address); // map this memory in with 2MB in size - uc_mem_map(handle, upper - 0x8000, 0x8000); + uc_mem_map_ex(handle, upper - 0x8000, 0x8000, UC_PROT_READ | UC_PROT_WRITE); // return true to indicate we want to continue return true; } - printf(">>> Missing memory is being WRITE at 0x%"PRIx64 ", data size = %u, data value = 0x%"PRIx64 "\n", + printf(">>> Missing memory is being WRITTEN at 0x%"PRIx64 ", data size = %u, data value = 0x%"PRIx64 "\n", + address, size, value); + return false; + case UC_MEM_WRITE_RO: + printf(">>> RO memory is being WRITTEN at 0x%"PRIx64 ", data size = %u, data value = 0x%"PRIx64 "\n", address, size, value); return false; } @@ -75,13 +86,6 @@ int main(int argc, char **argv, char **envp) { printf("Memory mapping test\n"); - if (map_stack) { - printf("Pre-mapping stack\n"); - } - else { - printf("Mapping stack on first invalid memory access\n"); - } - // Initialize emulator in X86-32bit mode err = uc_open(UC_ARCH_X86, UC_MODE_32, &handle); if (err) { @@ -95,8 +99,12 @@ int main(int argc, char **argv, char **envp) { uc_mem_map_ex(handle, 0x400000, 0x4000, UC_PROT_READ | UC_PROT_EXEC); if (map_stack) { + printf("Pre-mapping stack\n"); uc_mem_map_ex(handle, STACK, STACK_SIZE, UC_PROT_READ | UC_PROT_WRITE); } + else { + printf("Mapping stack on first invalid memory access\n"); + } esp = STACK + STACK_SIZE; @@ -117,21 +125,34 @@ int main(int argc, char **argv, char **envp) { uc_hook_add(handle, &trace1, UC_HOOK_MEM_INVALID, hook_mem_invalid, NULL); // emulate machine code in infinite time - printf("BEGIN execution\n"); - err = uc_emu_start(handle, 0x400000, 0x400000 + sizeof(PROGRAM), 0, 5); + printf("BEGIN execution - 1\n"); + err = uc_emu_start(handle, 0x400000, 0x400000 + sizeof(PROGRAM), 0, 10); if (err) { - printf("Failed on uc_emu_start() with error returned %u: %s\n", + printf("Expected failue on uc_emu_start() with error returned %u: %s\n", err, uc_strerror(err)); - return 3; } else { - printf("uc_emu_start returned UC_ERR_OK\n"); + printf("UNEXPECTED uc_emu_start returned UC_ERR_OK\n"); } - printf("END execution\n"); + printf("END execution - 1\n"); - printf("Verifying content at 0x40000f is unchanged\n"); - if (!uc_mem_read(handle, 0x40000f, bytes, 4)) { - printf(">>> Read 4 bytes from [0x%x] = 0x%x\n", (uint32_t)0x40000f, *(uint32_t*) bytes); + // emulate machine code in infinite time + printf("BEGIN execution - 2\n"); + uint32_t eax = 0x40002C; + uc_reg_write(handle, UC_X86_REG_EAX, &eax); + err = uc_emu_start(handle, 0x400015, 0x400000 + sizeof(PROGRAM), 0, 2); + if (err) { + printf("Expected failure on uc_emu_start() with error returned %u: %s\n", + err, uc_strerror(err)); + } + else { + printf("UNEXPECTED uc_emu_start returned UC_ERR_OK\n"); + } + printf("END execution - 2\n"); + + printf("Verifying content at 0x400025 is unchanged\n"); + if (!uc_mem_read(handle, 0x400025, bytes, 4)) { + printf(">>> Read 4 bytes from [0x%x] = 0x%x\n", (uint32_t)0x400025, *(uint32_t*) bytes); if (0x41414141 != *(uint32_t*) bytes) { printf("ERROR content in read only memory changed\n"); } @@ -144,6 +165,21 @@ int main(int argc, char **argv, char **envp) { return 4; } + printf("Verifying content at 0x40002C is unchanged\n"); + if (!uc_mem_read(handle, 0x40002C, bytes, 4)) { + printf(">>> Read 4 bytes from [0x%x] = 0x%x\n", (uint32_t)0x40002C, *(uint32_t*) bytes); + if (0x42424242 != *(uint32_t*) bytes) { + printf("ERROR content in read only memory changed\n"); + } + else { + printf("SUCCESS content in read only memory unchanged\n"); + } + } + else { + printf(">>> Failed to read 4 bytes from [0x%x]\n", (uint32_t)(esp - 4)); + return 4; + } + printf("Verifying content at bottom of stack is readable and correct\n"); if (!uc_mem_read(handle, esp - 4, bytes, 4)) { printf(">>> Read 4 bytes from [0x%x] = 0x%x\n", (uint32_t)(esp - 4), *(uint32_t*) bytes); diff --git a/uc.c b/uc.c index 5c5564cc..2db8461c 100755 --- a/uc.c +++ b/uc.c @@ -89,6 +89,8 @@ const char *uc_strerror(uc_err code) return "Invalid hook type (UC_ERR_HOOK)"; case UC_ERR_MAP: return "Invalid memory mapping (UC_ERR_MAP)"; + case UC_ERR_MEM_WRITE_RO: + return "Invalid memory write (UC_ERR_MEM_WRITE_RO)"; } } @@ -376,14 +378,14 @@ uc_err uc_mem_write(uch handle, uint64_t address, const uint8_t *bytes, size_t s if (mb == NULL) return UC_ERR_MEM_WRITE; - if (!(mb->perms & UC_PROT_WRITE)) //write protected + if (!(mb->region->perms & UC_PROT_WRITE)) //write protected //but this is not the program accessing memory, so temporarily mark writable uc->readonly_mem(mb->region, false); if (uc->write_mem(&uc->as, address, bytes, size) == false) return UC_ERR_MEM_WRITE; - if (!(mb->perms & UC_PROT_WRITE)) //write protected + if (!(mb->region->perms & UC_PROT_WRITE)) //write protected //now write protect it again uc->readonly_mem(mb->region, true); @@ -586,7 +588,6 @@ uc_err uc_mem_map_ex(uch handle, uint64_t address, size_t size, uint32_t perms) } uc->mapped_blocks[uc->mapped_block_count].end = address + size; //TODO extend uc_mem_map to accept permissions, figure out how to pass this down to qemu - uc->mapped_blocks[uc->mapped_block_count].perms = perms; uc->mapped_blocks[uc->mapped_block_count].region = uc->memory_map(uc, address, size, perms); uc->mapped_block_count++; @@ -600,17 +601,17 @@ uc_err uc_mem_map(uch handle, uint64_t address, size_t size) return uc_mem_map_ex(handle, address, size, UC_PROT_READ | UC_PROT_WRITE | UC_PROT_EXEC); } -bool memory_mapping(struct uc_struct* uc, uint64_t address) +MemoryRegion *memory_mapping(struct uc_struct* uc, uint64_t address) { unsigned int i; for(i = 0; i < uc->mapped_block_count; i++) { if (address >= uc->mapped_blocks[i].region->addr && address < uc->mapped_blocks[i].end) - return true; + return uc->mapped_blocks[i].region; } // not found - return false; + return NULL; } static uc_err _hook_mem_invalid(struct uc_struct* uc, uc_cb_eventmem_t callback, @@ -777,4 +778,3 @@ uc_err uc_hook_del(uch handle, uch *h2) return hook_del(handle, h2); } -