diff --git a/include/uc_priv.h b/include/uc_priv.h index 7a51767b..ba800932 100644 --- a/include/uc_priv.h +++ b/include/uc_priv.h @@ -47,6 +47,8 @@ typedef void (*uc_args_uc_u64_t)(struct uc_struct *, uint64_t addr); typedef MemoryRegion* (*uc_args_uc_ram_size_t)(struct uc_struct*, ram_addr_t begin, size_t size, uint32_t perms); +typedef void (*uc_mem_unmap_t)(struct uc_struct*, MemoryRegion *mr); + typedef void (*uc_readonly_mem_t)(MemoryRegion *mr, bool readonly); // which interrupt should make emulation stop? @@ -90,6 +92,7 @@ struct uc_struct { uc_args_tcg_enable_t tcg_enabled; uc_args_uc_long_t tcg_exec_init; uc_args_uc_ram_size_t memory_map; + uc_mem_unmap_t memory_unmap; uc_readonly_mem_t readonly_mem; // list of cpu void* cpu; @@ -172,8 +175,9 @@ struct uc_struct { bool block_full; MemoryRegion **mapped_blocks; uint32_t mapped_block_count; - void *qemu_thread_data; // to support cross compile to Windows (qemu-thread-win32.c) + uint32_t target_page_size; + uint32_t target_page_align; }; #include "qemu_macro.h" diff --git a/include/unicorn/unicorn.h b/include/unicorn/unicorn.h index f58ffcd3..cbac0506 100644 --- a/include/unicorn/unicorn.h +++ b/include/unicorn/unicorn.h @@ -104,7 +104,7 @@ typedef enum uc_mode { // These are values returned by uc_errno() typedef enum uc_err { UC_ERR_OK = 0, // No error: everything was fine - UC_ERR_OOM, // Out-Of-Memory error: uc_open(), uc_emulate() + UC_ERR_NOMEM, // Out-Of-Memory error: uc_open(), uc_emulate() UC_ERR_ARCH, // Unsupported architecture: uc_open() UC_ERR_HANDLE, // Invalid handle UC_ERR_UCH, // Invalid handle (uch) @@ -116,8 +116,10 @@ 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_NW, // Quit emulation due to write to non-writable: uc_emu_start() - UC_ERR_MEM_READ_NR, // Quit emulation due to read from non-readable: uc_emu_start() + UC_ERR_WRITE_PROT, // Quit emulation due to UC_PROT_WRITE violation: uc_emu_start() + UC_ERR_READ_PROT, // Quit emulation due to UC_PROT_READ violation: uc_emu_start() + UC_ERR_EXEC_PROT, // Quit emulation due to UC_PROT_EXEC violation: uc_emu_start() + UC_ERR_INVAL, // Inavalid argument provided to uc_xxx function (See specific function API) } uc_err; @@ -149,8 +151,9 @@ 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_NW, // write to non-writable - UC_MEM_READ_NR, // read from non-readable + UC_MEM_WRITE_PROT, // write to write protected memory + UC_MEM_READ_PROT, // read from read protected memory + UC_MEM_EXEC_PROT, // fetch from non-executable memory } uc_mem_type; // All type of hooks for uc_hook_add() API. @@ -392,27 +395,65 @@ typedef enum uc_prot { UC_PROT_NONE = 0, UC_PROT_READ = 1, UC_PROT_WRITE = 2, - UC_PROT_ALL = 3, + UC_PROT_EXEC = 4, + UC_PROT_ALL = 7, } uc_prot; /* Map memory in for emulation. - This API adds a memory region that can be used by emulation. + This API adds a memory region that can be used by emulation. The region is mapped + with permissions UC_PROT_READ | UC_PROT_WRITE | UC_PROT_EXEC. @handle: handle returned by uc_open() @address: starting address of the new memory region to be mapped in. - This address must be aligned to 4KB, or this will return with UC_ERR_MAP error. + This address must be aligned to 4KB, or this will return with UC_ERR_INVAL error. @size: size of the new memory region to be mapped in. - This size must be multiple of 4KB, or this will return with UC_ERR_MAP error. + This size must be multiple of 4KB, or this will return with UC_ERR_INVAL error. @perms: Permissions for the newly mapped region. - This must be some combination of UC_PROT_READ & UC_PROT_WRITE, - or this will return with UC_ERR_MAP error. See uc_prot type above. + This must be some combination of UC_PROT_READ | UC_PROT_WRITE | UC_PROT_EXEC, + or this will return with UC_ERR_INVAL error. + + @return UC_ERR_OK on success, UC_ERR_NOMEM if no memory is available to satisfy the + request, or other value on failure (refer to uc_err enum for detailed error). +*/ +UNICORN_EXPORT +uc_err uc_mem_map(uch handle, uint64_t address, size_t size, uint32_t perms); + +/* + Set memory permissions for emulation memory. + This API changes permissions on an existing memory region. + + @handle: handle returned by uc_open() + @address: starting address of the memory region to be modified. + This address must be aligned to 4KB, or this will return with UC_ERR_INVAL error. + @size: size of the memory region to be modified. + This size must be multiple of 4KB, or this will return with UC_ERR_INVAL error. + @perms: New permissions for the mapped region. + This must be some combination of UC_PROT_READ | UC_PROT_WRITE | UC_PROT_EXEC, + or this will return with UC_ERR_INVAL error. + + @return UC_ERR_OK on success, UC_ERR_HANDLE for an invalid handle, UC_ERR_INVAL + for invalid perms or unaligned address or size, UC_ERR_NOMEM if entire region + is not mapped. +*/ +UNICORN_EXPORT +uc_err uc_mem_protect(uch handle, uint64_t address, size_t size, uint32_t perms); + +/* + Unmap a region of emulation memory. + This API deletes a memory mapping from the emulation memory space. + + @handle: handle returned by uc_open() + @address: starting address of the memory region to be unmapped. + This address must be aligned to 4KB, or this will return with UC_ERR_INVAL error. + @size: size of the memory region to be modified. + This size must be multiple of 4KB, or this will return with UC_ERR_INVAL error. @return UC_ERR_OK on success, or other value on failure (refer to uc_err enum for detailed error). */ UNICORN_EXPORT -uc_err uc_mem_map(uch handle, uint64_t address, size_t size, uint32_t perms); +uc_err uc_mem_unmap(uch handle, uint64_t address, size_t size); #ifdef __cplusplus } diff --git a/qemu/aarch64.h b/qemu/aarch64.h index 9ab06273..77de1644 100644 --- a/qemu/aarch64.h +++ b/qemu/aarch64.h @@ -7,6 +7,7 @@ #define phys_mem_clean phys_mem_clean_aarch64 #define tb_cleanup tb_cleanup_aarch64 #define memory_map memory_map_aarch64 +#define memory_unmap memory_unmap_aarch64 #define memory_free memory_free_aarch64 #define helper_raise_exception helper_raise_exception_aarch64 #define tcg_enabled tcg_enabled_aarch64 diff --git a/qemu/arm.h b/qemu/arm.h index c24329b3..3d405fac 100644 --- a/qemu/arm.h +++ b/qemu/arm.h @@ -7,6 +7,7 @@ #define phys_mem_clean phys_mem_clean_arm #define tb_cleanup tb_cleanup_arm #define memory_map memory_map_arm +#define memory_unmap memory_unmap_arm #define memory_free memory_free_arm #define helper_raise_exception helper_raise_exception_arm #define tcg_enabled tcg_enabled_arm diff --git a/qemu/cputlb.c b/qemu/cputlb.c index cde8e30e..ed120082 100644 --- a/qemu/cputlb.c +++ b/qemu/cputlb.c @@ -299,6 +299,11 @@ tb_page_addr_t get_page_addr_code(CPUArchState *env1, target_ulong addr) if (unlikely(env1->tlb_table[mmu_idx][page_index].addr_code != (addr & TARGET_PAGE_MASK))) { cpu_ldub_code(env1, addr); + //check for NX related error from softmmu + if (env1->invalid_error == UC_ERR_MEM_READ) { + env1->invalid_error = UC_ERR_CODE_INVALID; + return -1; + } } pd = env1->iotlb[mmu_idx][page_index] & ~TARGET_PAGE_MASK; mr = iotlb_to_region(cpu->as, pd); diff --git a/qemu/header_gen.py b/qemu/header_gen.py index ff8a1ca5..67982502 100644 --- a/qemu/header_gen.py +++ b/qemu/header_gen.py @@ -13,6 +13,7 @@ symbols = ( 'phys_mem_clean', 'tb_cleanup', 'memory_map', + 'memory_unmap', 'memory_free', 'helper_raise_exception', 'tcg_enabled', diff --git a/qemu/include/exec/memory.h b/qemu/include/exec/memory.h index 4df8bd85..45c51e4d 100644 --- a/qemu/include/exec/memory.h +++ b/qemu/include/exec/memory.h @@ -939,6 +939,7 @@ void address_space_unmap(AddressSpace *as, void *buffer, hwaddr len, void memory_register_types(struct uc_struct *uc); MemoryRegion *memory_map(struct uc_struct *uc, ram_addr_t begin, size_t size, uint32_t perms); +void memory_unmap(struct uc_struct *uc, MemoryRegion *mr); int memory_free(struct uc_struct *uc); #endif diff --git a/qemu/m68k.h b/qemu/m68k.h index 5dbefab7..4be757ba 100644 --- a/qemu/m68k.h +++ b/qemu/m68k.h @@ -7,6 +7,7 @@ #define phys_mem_clean phys_mem_clean_m68k #define tb_cleanup tb_cleanup_m68k #define memory_map memory_map_m68k +#define memory_unmap memory_unmap_m68k #define memory_free memory_free_m68k #define helper_raise_exception helper_raise_exception_m68k #define tcg_enabled tcg_enabled_m68k diff --git a/qemu/memory.c b/qemu/memory.c index 60f86ae4..e04d59b7 100644 --- a/qemu/memory.c +++ b/qemu/memory.c @@ -45,6 +45,18 @@ MemoryRegion *memory_map(struct uc_struct *uc, ram_addr_t begin, size_t size, ui return ram; } +void memory_unmap(struct uc_struct *uc, MemoryRegion *mr) +{ + target_ulong addr; + //make sure all pages associated with the MemoryRegion are flushed + for (addr = mr->addr; addr < mr->end; addr += uc->target_page_size) { + tlb_flush_page(uc->current_cpu, addr); + } + mr->enabled = false; + memory_region_del_subregion(get_system_memory(uc), mr); + g_free(mr); +} + int memory_free(struct uc_struct *uc) { int i; diff --git a/qemu/mips.h b/qemu/mips.h index 059995e5..7a3e308a 100644 --- a/qemu/mips.h +++ b/qemu/mips.h @@ -7,6 +7,7 @@ #define phys_mem_clean phys_mem_clean_mips #define tb_cleanup tb_cleanup_mips #define memory_map memory_map_mips +#define memory_unmap memory_unmap_mips #define memory_free memory_free_mips #define helper_raise_exception helper_raise_exception_mips #define tcg_enabled tcg_enabled_mips diff --git a/qemu/mips64.h b/qemu/mips64.h index 74daddbc..9870cb15 100644 --- a/qemu/mips64.h +++ b/qemu/mips64.h @@ -7,6 +7,7 @@ #define phys_mem_clean phys_mem_clean_mips64 #define tb_cleanup tb_cleanup_mips64 #define memory_map memory_map_mips64 +#define memory_unmap memory_unmap_mips64 #define memory_free memory_free_mips64 #define helper_raise_exception helper_raise_exception_mips64 #define tcg_enabled tcg_enabled_mips64 diff --git a/qemu/mips64el.h b/qemu/mips64el.h index 6ffc2dbc..5fde9e53 100644 --- a/qemu/mips64el.h +++ b/qemu/mips64el.h @@ -7,6 +7,7 @@ #define phys_mem_clean phys_mem_clean_mips64el #define tb_cleanup tb_cleanup_mips64el #define memory_map memory_map_mips64el +#define memory_unmap memory_unmap_mips64el #define memory_free memory_free_mips64el #define helper_raise_exception helper_raise_exception_mips64el #define tcg_enabled tcg_enabled_mips64el diff --git a/qemu/mipsel.h b/qemu/mipsel.h index 94c4fdf7..caf1fe4d 100644 --- a/qemu/mipsel.h +++ b/qemu/mipsel.h @@ -7,6 +7,7 @@ #define phys_mem_clean phys_mem_clean_mipsel #define tb_cleanup tb_cleanup_mipsel #define memory_map memory_map_mipsel +#define memory_unmap memory_unmap_mipsel #define memory_free memory_free_mipsel #define helper_raise_exception helper_raise_exception_mipsel #define tcg_enabled tcg_enabled_mipsel diff --git a/qemu/powerpc.h b/qemu/powerpc.h index fd627665..92e614e1 100644 --- a/qemu/powerpc.h +++ b/qemu/powerpc.h @@ -7,6 +7,7 @@ #define phys_mem_clean phys_mem_clean_powerpc #define tb_cleanup tb_cleanup_powerpc #define memory_map memory_map_powerpc +#define memory_unmap memory_unmap_powerpc #define memory_free memory_free_powerpc #define helper_raise_exception helper_raise_exception_powerpc #define tcg_enabled tcg_enabled_powerpc diff --git a/qemu/softmmu_template.h b/qemu/softmmu_template.h index 3def3e76..3695c64e 100644 --- a/qemu/softmmu_template.h +++ b/qemu/softmmu_template.h @@ -181,6 +181,24 @@ WORD_TYPE helper_le_ld_name(CPUArchState *env, target_ulong addr, int mmu_idx, struct uc_struct *uc = env->uc; MemoryRegion *mr = memory_mapping(uc, addr); +#if defined(SOFTMMU_CODE_ACCESS) + // Unicorn: callback on fetch from NX + if (mr != NULL && !(mr->perms & UC_PROT_EXEC)) { //non-executable + if (uc->hook_mem_idx != 0 && ((uc_cb_eventmem_t)uc->hook_callbacks[uc->hook_mem_idx].callback)( + (uch)uc, UC_MEM_EXEC_PROT, addr, DATA_SIZE, 0, + uc->hook_callbacks[uc->hook_mem_idx].user_data)) { + env->invalid_error = UC_ERR_OK; + } + else { + env->invalid_addr = addr; + env->invalid_error = UC_ERR_EXEC_PROT; + // printf("***** Invalid fetch (non-executable) at " TARGET_FMT_lx "\n", addr); + cpu_exit(uc->current_cpu); + return 0; + } + } +#endif + // Unicorn: callback on memory read if (env->uc->hook_mem_read && READ_ACCESS_TYPE == MMU_DATA_LOAD) { struct hook_struct *trace = hook_find((uch)env->uc, UC_HOOK_MEM_READ, addr); @@ -206,20 +224,16 @@ WORD_TYPE helper_le_ld_name(CPUArchState *env, target_ulong addr, int mmu_idx, } } - // Unicorn: callback on read only memory + // Unicorn: callback on non-readable memory if (mr != NULL && !(mr->perms & UC_PROT_READ)) { //non-readable - 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_READ_NR, addr, DATA_SIZE, 0, - uc->hook_callbacks[uc->hook_mem_idx].user_data); - } - if (result) { + if (uc->hook_mem_idx != 0 && ((uc_cb_eventmem_t)uc->hook_callbacks[uc->hook_mem_idx].callback)( + (uch)uc, UC_MEM_READ_PROT, addr, DATA_SIZE, 0, + uc->hook_callbacks[uc->hook_mem_idx].user_data)) { env->invalid_error = UC_ERR_OK; } else { env->invalid_addr = addr; - env->invalid_error = UC_ERR_MEM_READ_NR; + env->invalid_error = UC_ERR_READ_PROT; // printf("***** Invalid memory read (non-readable) at " TARGET_FMT_lx "\n", addr); cpu_exit(uc->current_cpu); return 0; @@ -326,6 +340,24 @@ WORD_TYPE helper_be_ld_name(CPUArchState *env, target_ulong addr, int mmu_idx, struct uc_struct *uc = env->uc; MemoryRegion *mr = memory_mapping(uc, addr); +#if defined(SOFTMMU_CODE_ACCESS) + // Unicorn: callback on fetch from NX + if (mr != NULL && !(mr->perms & UC_PROT_EXEC)) { //non-executable + if (uc->hook_mem_idx != 0 && ((uc_cb_eventmem_t)uc->hook_callbacks[uc->hook_mem_idx].callback)( + (uch)uc, UC_MEM_EXEC_PROT, addr, DATA_SIZE, 0, + uc->hook_callbacks[uc->hook_mem_idx].user_data)) { + env->invalid_error = UC_ERR_OK; + } + else { + env->invalid_addr = addr; + env->invalid_error = UC_ERR_EXEC_PROT; + // printf("***** Invalid fetch (non-executable) at " TARGET_FMT_lx "\n", addr); + cpu_exit(uc->current_cpu); + return 0; + } + } +#endif + // Unicorn: callback on memory read if (env->uc->hook_mem_read && READ_ACCESS_TYPE == MMU_DATA_LOAD) { struct hook_struct *trace = hook_find((uch)env->uc, UC_HOOK_MEM_READ, addr); @@ -351,20 +383,16 @@ WORD_TYPE helper_be_ld_name(CPUArchState *env, target_ulong addr, int mmu_idx, } } - // Unicorn: callback on read only memory + // Unicorn: callback on non-readable memory if (mr != NULL && !(mr->perms & UC_PROT_READ)) { //non-readable - 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_READ_NR, addr, DATA_SIZE, 0, - uc->hook_callbacks[uc->hook_mem_idx].user_data); - } - if (result) { + if (uc->hook_mem_idx != 0 && ((uc_cb_eventmem_t)uc->hook_callbacks[uc->hook_mem_idx].callback)( + (uch)uc, UC_MEM_READ_PROT, addr, DATA_SIZE, 0, + uc->hook_callbacks[uc->hook_mem_idx].user_data)) { env->invalid_error = UC_ERR_OK; } else { env->invalid_addr = addr; - env->invalid_error = UC_ERR_MEM_READ_NR; + env->invalid_error = UC_ERR_READ_PROT; // printf("***** Invalid memory read (non-readable) at " TARGET_FMT_lx "\n", addr); cpu_exit(uc->current_cpu); return 0; @@ -534,20 +562,16 @@ void helper_le_st_name(CPUArchState *env, target_ulong addr, DATA_TYPE val, } } - // 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_NW, addr, DATA_SIZE, (int64_t)val, - uc->hook_callbacks[uc->hook_mem_idx].user_data); - } - if (result) { + // Unicorn: callback on non-writable memory + if (mr != NULL && !(mr->perms & UC_PROT_WRITE)) { //non-writable + if (uc->hook_mem_idx != 0 && ((uc_cb_eventmem_t)uc->hook_callbacks[uc->hook_mem_idx].callback)( + (uch)uc, UC_MEM_WRITE_PROT, addr, DATA_SIZE, (int64_t)val, + uc->hook_callbacks[uc->hook_mem_idx].user_data)) { env->invalid_error = UC_ERR_OK; } else { env->invalid_addr = addr; - env->invalid_error = UC_ERR_MEM_WRITE_NW; + env->invalid_error = UC_ERR_WRITE_PROT; // printf("***** Invalid memory write (ro) at " TARGET_FMT_lx "\n", addr); cpu_exit(uc->current_cpu); return; @@ -672,20 +696,16 @@ void helper_be_st_name(CPUArchState *env, target_ulong addr, DATA_TYPE val, } } - // 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_NW, addr, DATA_SIZE, (int64_t)val, - uc->hook_callbacks[uc->hook_mem_idx].user_data); - } - if (result) { + // Unicorn: callback on non-writable memory + if (mr != NULL && !(mr->perms & UC_PROT_WRITE)) { //non-writable + if (uc->hook_mem_idx != 0 && ((uc_cb_eventmem_t)uc->hook_callbacks[uc->hook_mem_idx].callback)( + (uch)uc, UC_MEM_WRITE_PROT, addr, DATA_SIZE, (int64_t)val, + uc->hook_callbacks[uc->hook_mem_idx].user_data)) { env->invalid_error = UC_ERR_OK; } else { env->invalid_addr = addr; - env->invalid_error = UC_ERR_MEM_WRITE_NW; + env->invalid_error = UC_ERR_WRITE_PROT; // printf("***** Invalid memory write (ro) at " TARGET_FMT_lx "\n", addr); cpu_exit(uc->current_cpu); return; diff --git a/qemu/sparc.h b/qemu/sparc.h index 64803c11..6aa47aa5 100644 --- a/qemu/sparc.h +++ b/qemu/sparc.h @@ -7,6 +7,7 @@ #define phys_mem_clean phys_mem_clean_sparc #define tb_cleanup tb_cleanup_sparc #define memory_map memory_map_sparc +#define memory_unmap memory_unmap_sparc #define memory_free memory_free_sparc #define helper_raise_exception helper_raise_exception_sparc #define tcg_enabled tcg_enabled_sparc diff --git a/qemu/sparc64.h b/qemu/sparc64.h index 5042c38d..6d3d2a1d 100644 --- a/qemu/sparc64.h +++ b/qemu/sparc64.h @@ -7,6 +7,7 @@ #define phys_mem_clean phys_mem_clean_sparc64 #define tb_cleanup tb_cleanup_sparc64 #define memory_map memory_map_sparc64 +#define memory_unmap memory_unmap_sparc64 #define memory_free memory_free_sparc64 #define helper_raise_exception helper_raise_exception_sparc64 #define tcg_enabled tcg_enabled_sparc64 diff --git a/qemu/unicorn_common.h b/qemu/unicorn_common.h index 176900cb..5ba74fac 100644 --- a/qemu/unicorn_common.h +++ b/qemu/unicorn_common.h @@ -73,8 +73,12 @@ static inline void uc_common_init(struct uc_struct* uc) uc->pause_all_vcpus = pause_all_vcpus; uc->vm_start = vm_start; uc->memory_map = memory_map; + uc->memory_unmap = memory_unmap; uc->readonly_mem = memory_region_set_readonly; + uc->target_page_size = TARGET_PAGE_SIZE; + uc->target_page_align = TARGET_PAGE_SIZE - 1; + if (!uc->release) uc->release = release_common; } diff --git a/qemu/x86_64.h b/qemu/x86_64.h index 9a148e95..ac9f34f0 100644 --- a/qemu/x86_64.h +++ b/qemu/x86_64.h @@ -7,6 +7,7 @@ #define phys_mem_clean phys_mem_clean_x86_64 #define tb_cleanup tb_cleanup_x86_64 #define memory_map memory_map_x86_64 +#define memory_unmap memory_unmap_x86_64 #define memory_free memory_free_x86_64 #define helper_raise_exception helper_raise_exception_x86_64 #define tcg_enabled tcg_enabled_x86_64 diff --git a/regress/ro_mem_test.c b/regress/ro_mem_test.c index 06edf068..52534cd4 100644 --- a/regress/ro_mem_test.c +++ b/regress/ro_mem_test.c @@ -156,8 +156,11 @@ int main(int argc, char **argv, char **envp) // emulate machine code in infinite time printf("BEGIN execution - 2\n"); + //update eax to point to aligned memory (same as add eax,7 above) uint32_t eax = 0x40002C; uc_reg_write(handle, UC_X86_REG_EAX, &eax); + //resume execution at the mov dword [eax], 0x87654321 + //to test an aligned write as well 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", diff --git a/samples/Makefile b/samples/Makefile index 1ebb891f..f6345ae8 100644 --- a/samples/Makefile +++ b/samples/Makefile @@ -97,6 +97,9 @@ endif ifneq (,$(findstring x86,$(UNICORN_ARCHS))) SOURCES += sample_x86.c SOURCES += shellcode.c +SOURCES += mem_unmap.c +SOURCES += mem_protect.c +SOURCES += mem_exec.c endif ifneq (,$(findstring m68k,$(UNICORN_ARCHS))) SOURCES += sample_m68k.c @@ -111,7 +114,8 @@ all: $(BINARY) clean: rm -rf *.o $(OBJS_ELF) $(BINARY) $(SAMPLEDIR)/*.exe $(SAMPLEDIR)/*.static $(OBJDIR)/lib$(LIBNAME)* $(OBJDIR)/$(LIBNAME)* rm -rf libunicorn*.so libunicorn*.lib libunicorn*.dylib unicorn*.dll unicorn*.lib - rm -rf sample_x86 sample_arm sample_arm64 sample_mips sample_sparc sample_ppc sample_m68k shellcode + rm -rf sample_x86 sample_arm sample_arm64 sample_mips sample_sparc sample_ppc sample_m68k \ + shellcode mem_unmap mem_protect mem_exec $(BINARY): $(OBJS) diff --git a/samples/mem_exec.c b/samples/mem_exec.c new file mode 100644 index 00000000..171022a7 --- /dev/null +++ b/samples/mem_exec.c @@ -0,0 +1,296 @@ +/* + +Executable memory regions demo / unit test + +Copyright(c) 2015 Chris Eagle + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +version 2 as published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ + +#define __STDC_FORMAT_MACROS +#include +#include +#include +#include +#include +#include + +#include + +unsigned char PROGRAM[] = + "\xeb\x45\x5e\x81\xe6\x00\xf0\xff\xff\x40\x40\x40\x40\x40\x40\x40" + "\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40" + "\x40\x40\x40\x40\x40\x40\x40\x89\xf7\x81\xc7\x00\x00\x10\x00\xb9" + "\x4c\x00\x00\x00\x81\xff\x00\x00\x40\x00\x75\x01\xf4\xf3\xa4\x81" + "\xe7\x00\xf0\xff\xff\xff\xe7\xe8\xb6\xff\xff\xff"; +// total size: 76 bytes + +/* +bits 32 + +; assumes r-x section at 0x100000 +; assumes rw- section at 0x200000 +; assumes r-- section at 0x300000 +; also needs an initialized stack + +start: + jmp bottom +top: + pop esi + and esi, ~0xfff + times 30 inc eax + mov edi, esi + add edi, 0x100000 + mov ecx, end - start + rep movsb + and edi, ~0xfff + cmp edi, 0x400000 + jnz next_block + hlt +next_block: + jmp edi +bottom: + call top +end: +*/ + +int test_num = 0; +uint32_t tests[] = { + 0x41414141, + 0x43434343, + 0x45454545 +}; + +static int log_num = 1; + +#define CODE_SECTION 0x100000 +#define CODE_SIZE 0x1000 + +// callback for tracing instruction +static void hook_code(uch handle, uint64_t addr, uint32_t size, void *user_data) +{ + uint8_t opcode; + if (uc_mem_read(handle, addr, &opcode, 1) != UC_ERR_OK) { + printf("not ok %d - uc_mem_read fail during hook_code callback, addr: 0x%" PRIx64 "\n", log_num++, addr); + } +// printf("ok %d - uc_mem_read for opcode at address 0x%" PRIx64 "\n", log_num++, addr); + switch (opcode) { + case 0xf4: //hlt + printf("# Handling HLT\n"); + if (uc_emu_stop(handle) != UC_ERR_OK) { + printf("not ok %d - uc_emu_stop fail during hook_code callback, addr: 0x%" PRIx64 "\n", log_num++, addr); + _exit(-1); + } + else { + printf("ok %d - hlt encountered, uc_emu_stop called\n", log_num++); + } + break; + default: //all others +// printf("# Handling OTHER\n"); + break; + } +} + +// callback for tracing memory access (READ or WRITE) +static void hook_mem_write(uch handle, uc_mem_type type, + uint64_t addr, int size, int64_t value, void *user_data) +{ + printf("# write to memory at 0x%"PRIx64 ", data size = %u, data value = 0x%"PRIx64 "\n", addr, size, value); +} + +// callback for tracing invalid memory access (READ or WRITE) +static bool hook_mem_invalid(uch handle, uc_mem_type type, + uint64_t addr, int size, int64_t value, void *user_data) +{ + switch(type) { + default: + printf("not ok %d - UC_HOOK_MEM_INVALID type: %d at 0x%" PRIx64 "\n", log_num++, type, addr); + return false; + case UC_MEM_EXEC_PROT: + printf("# Fetch from non-executable memory at 0x%"PRIx64 "\n", addr); + + //make page executable + if (uc_mem_protect(handle, addr & ~0xfffL, 0x1000, UC_PROT_READ | UC_PROT_EXEC) != UC_ERR_OK) { + printf("not ok %d - uc_mem_protect fail for address: 0x%" PRIx64 "\n", log_num++, addr); + } + else { + printf("ok %d - uc_mem_protect success at 0x%" PRIx64 "\n", log_num++, addr); + } + return true; + case UC_MEM_WRITE_PROT: + printf("# write to non-writeable memory at 0x%"PRIx64 ", data size = %u, data value = 0x%"PRIx64 "\n", addr, size, value); + + if (uc_mem_protect(handle, addr & ~0xfffL, 0x1000, UC_PROT_READ | UC_PROT_WRITE) != UC_ERR_OK) { + printf("not ok %d - uc_mem_protect fail during hook_mem_invalid callback, addr: 0x%" PRIx64 "\n", log_num++, addr); + } + else { + printf("ok %d - uc_mem_protect success\n", log_num++); + } + return true; + } +} + +int main(int argc, char **argv, char **envp) +{ + uch handle, trace1, trace2; + uc_err err; + uint32_t esp, eip; + int32_t buf1[1024], buf2[1024], readbuf[1024]; + int i; + + //don't really care about quality of randomness + srand(time(NULL)); + for (i = 0; i < 1024; i++) { + buf1[i] = rand(); + buf2[i] = rand(); + } + + printf("# Memory protect test\n"); + + // Initialize emulator in X86-32bit mode + err = uc_open(UC_ARCH_X86, UC_MODE_32, &handle); + if (err) { + printf("not ok %d - Failed on uc_open() with error returned: %u\n", log_num++, err); + return 1; + } + else { + printf("ok %d - uc_open() success\n", log_num++); + } + + uc_mem_map(handle, 0x100000, 0x1000, UC_PROT_READ | UC_PROT_EXEC); + uc_mem_map(handle, 0x1ff000, 0x2000, UC_PROT_READ | UC_PROT_WRITE); + uc_mem_map(handle, 0x300000, 0x2000, UC_PROT_READ); + uc_mem_map(handle, 0xf00000, 0x1000, UC_PROT_READ | UC_PROT_WRITE); + + esp = 0xf00000 + 0x1000; + + // Setup stack pointer + if (uc_reg_write(handle, UC_X86_REG_ESP, &esp)) { + printf("not ok %d - Failed to set esp. quit!\n", log_num++); + return 2; + } + else { + printf("ok %d - ESP set\n", log_num++); + } + + // fill in sections that shouldn't get touched + if (uc_mem_write(handle, 0x1ff000, (uint8_t*)buf1, 4096)) { + printf("not ok %d - Failed to write random buffer 1 to memory, quit!\n", log_num++); + return 3; + } + else { + printf("ok %d - Random buffer 1 written to memory\n", log_num++); + } + + if (uc_mem_write(handle, 0x301000, (uint8_t*)buf2, 4096)) { + printf("not ok %d - Failed to write random buffer 2 to memory, quit!\n", log_num++); + return 4; + } + else { + printf("ok %d - Random buffer 2 written to memory\n", log_num++); + } + + // write machine code to be emulated to memory + if (uc_mem_write(handle, 0x100000, PROGRAM, sizeof(PROGRAM))) { + printf("not ok %d - Failed to write emulation code to memory, quit!\n", log_num++); + return 5; + } + else { + printf("ok %d - Program written to memory\n", log_num++); + } + + if (uc_hook_add(handle, &trace2, UC_HOOK_CODE, hook_code, NULL, 1, 0) != UC_ERR_OK) { + printf("not ok %d - Failed to install UC_HOOK_CODE handler\n", log_num++); + return 6; + } + else { + printf("ok %d - UC_HOOK_CODE installed\n", log_num++); + } + + // intercept memory write events + if (uc_hook_add(handle, &trace1, UC_HOOK_MEM_WRITE, hook_mem_write, NULL) != UC_ERR_OK) { + printf("not ok %d - Failed to install UC_HOOK_MEM_WRITE handler\n", log_num++); + return 7; + } + else { + printf("ok %d - UC_HOOK_MEM_WRITE installed\n", log_num++); + } + + // intercept invalid memory events + if (uc_hook_add(handle, &trace1, UC_HOOK_MEM_INVALID, hook_mem_invalid, NULL) != UC_ERR_OK) { + printf("not ok %d - Failed to install UC_HOOK_MEM_INVALID handler\n", log_num++); + return 8; + } + else { + printf("ok %d - UC_HOOK_MEM_INVALID installed\n", log_num++); + } + + // emulate machine code until told to stop by hook_code + printf("# BEGIN execution\n"); + err = uc_emu_start(handle, 0x100000, 0x400000, 0, 0); + if (err != UC_ERR_OK) { + printf("not ok %d - Failure on uc_emu_start() with error %u:%s\n", log_num++, err, uc_strerror(err)); + return 9; + } + else { + printf("ok %d - uc_emu_start complete\n", log_num++); + } + printf("# END execution\n"); + + // get ending EIP + if (uc_reg_read(handle, UC_X86_REG_EIP, &eip)) { + printf("not ok %d - Failed to read eip.\n", log_num++); + } + else { + printf("ok %d - Ending EIP 0x%x\n", log_num++, eip); + } + + //make sure that random blocks didn't get nuked + // fill in sections that shouldn't get touched + if (uc_mem_read(handle, 0x1ff000, (uint8_t*)readbuf, 4096)) { + printf("not ok %d - Failed to read random buffer 1 from memory\n", log_num++); + } + else { + printf("ok %d - Random buffer 1 read from memory\n", log_num++); + if (memcmp(buf1, readbuf, 4096)) { + printf("not ok %d - Random buffer 1 contents are incorrect\n", log_num++); + } + else { + printf("ok %d - Random buffer 1 contents are correct\n", log_num++); + } + } + + if (uc_mem_read(handle, 0x301000, (uint8_t*)readbuf, 4096)) { + printf("not ok %d - Failed to read random buffer 2 from memory\n", log_num++); + } + else { + printf("ok %d - Random buffer 2 read from memory\n", log_num++); + if (memcmp(buf2, readbuf, 4096)) { + printf("not ok %d - Random buffer 2 contents are incorrect\n", log_num++); + } + else { + printf("ok %d - Random buffer 2 contents are correct\n", log_num++); + } + } + + if (uc_close(&handle) == UC_ERR_OK) { + printf("ok %d - uc_close complete\n", log_num++); + } + else { + printf("not ok %d - uc_close complete\n", log_num++); + } + + return 0; +} diff --git a/samples/mem_protect.c b/samples/mem_protect.c new file mode 100644 index 00000000..5d852ff5 --- /dev/null +++ b/samples/mem_protect.c @@ -0,0 +1,323 @@ +/* + +uc_mem_protect demo / unit test + +Copyright(c) 2015 Chris Eagle + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +version 2 as published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ + +#define __STDC_FORMAT_MACROS +#include +#include +#include +#include +#include +#include + +#include + +unsigned char PROGRAM[] = + "\xc7\x05\x00\x00\x20\x00\x41\x41\x41\x41\x90\xc7\x05\x00\x00\x20" + "\x00\x42\x42\x42\x42\xc7\x05\x00\x00\x30\x00\x43\x43\x43\x43\x90" + "\xc7\x05\x00\x00\x30\x00\x44\x44\x44\x44\xc7\x05\x00\x00\x40\x00" + "\x45\x45\x45\x45\x90\xc7\x05\x00\x00\x40\x00\x46\x46\x46\x46\xc7" + "\x05\x00\xf8\x3f\x00\x47\x47\x47\x47\xc7\x05\x00\x18\x40\x00\x48" + "\x48\x48\x48\xf4"; +// total size: 84 bytes + +/* +bits 32 + +; assumes code section at 0x100000 +; assumes data section at 0x200000, initially rw +; assumes data section at 0x300000, initially rw +; assumes data section at 0x400000, initially rw + +; with installed hooks unmaps or maps on each nop + + mov dword [0x200000], 0x41414141 + nop ; mark it RO + mov dword [0x200000], 0x42424242 + + mov dword [0x300000], 0x43434343 + nop ; mark it RO + mov dword [0x300000], 0x44444444 + + mov dword [0x400000], 0x45454545 + nop ; mark it RO + mov dword [0x400000], 0x46464646 + mov dword [0x3ff800], 0x47474747 ; make sure surrounding areas remained RW + mov dword [0x401800], 0x48484848 ; make sure surrounding areas remained RW + + hlt ; tell hook function we are done +*/ + +int test_num = 0; +uint32_t tests[] = { + 0x41414141, + 0x43434343, + 0x45454545 +}; + +static int log_num = 1; + +#define CODE_SECTION 0x100000 +#define CODE_SIZE 0x1000 + +// callback for tracing instruction +static void hook_code(uch handle, uint64_t addr, uint32_t size, void *user_data) +{ + uint8_t opcode; + uint32_t testval; + if (uc_mem_read(handle, addr, &opcode, 1) != UC_ERR_OK) { + printf("not ok %d - uc_mem_read fail during hook_code callback, addr: 0x%" PRIx64 "\n", log_num++, addr); + } + printf("ok %d - uc_mem_read for opcode at address 0x%" PRIx64 "\n", log_num++, addr); + switch (opcode) { + case 0x90: //nop + printf("# Handling NOP\n"); + if (uc_mem_read(handle, 0x200000 + test_num * 0x100000, (uint8_t*)&testval, sizeof(testval)) != UC_ERR_OK) { + printf("not ok %d - uc_mem_read fail for address: 0x%x\n", log_num++, 0x200000 + test_num * 0x100000); + } + else { + printf("ok %d - good uc_mem_read for address: 0x%x\n", log_num++, 0x200000 + test_num * 0x100000); + printf("# uc_mem_read for test %d\n", test_num); + + if (testval == tests[test_num]) { + printf("ok %d - passed test %d\n", log_num++, test_num); + } + else { + printf("not ok %d - failed test %d\n", log_num++, test_num); + printf("# Expected: 0x%x\n",tests[test_num]); + printf("# Received: 0x%x\n", testval); + } + } + if (uc_mem_protect(handle, 0x200000 + test_num * 0x100000, 0x1000, UC_PROT_READ) != UC_ERR_OK) { + printf("not ok %d - uc_mem_protect fail during hook_code callback, addr: 0x%x\n", log_num++, 0x200000 + test_num * 0x100000); + } + else { + printf("ok %d - uc_mem_protect success\n", log_num++); + } + test_num++; + break; + case 0xf4: //hlt + printf("# Handling HLT\n"); + if (uc_emu_stop(handle) != UC_ERR_OK) { + printf("not ok %d - uc_emu_stop fail during hook_code callback, addr: 0x%" PRIx64 "\n", log_num++, addr); + _exit(-1); + } + else { + printf("ok %d - hlt encountered, uc_emu_stop called\n", log_num++); + } + break; + default: //all others + printf("# Handling OTHER\n"); + break; + } +} + +// callback for tracing memory access (READ or WRITE) +static void hook_mem_write(uch handle, uc_mem_type type, + uint64_t addr, int size, int64_t value, void *user_data) +{ + printf("# write to memory at 0x%"PRIx64 ", data size = %u, data value = 0x%"PRIx64 "\n", addr, size, value); +} + +// callback for tracing invalid memory access (READ or WRITE) +static bool hook_mem_invalid(uch handle, uc_mem_type type, + uint64_t addr, int size, int64_t value, void *user_data) +{ + uint32_t testval; + switch(type) { + default: + printf("not ok %d - UC_HOOK_MEM_INVALID type: %d at 0x%" PRIx64 "\n", log_num++, type, addr); + return false; + case UC_MEM_WRITE_PROT: + printf("# write to non-writeable memory at 0x%"PRIx64 ", data size = %u, data value = 0x%"PRIx64 "\n", addr, size, value); + + if (uc_mem_read(handle, addr, (uint8_t*)&testval, sizeof(testval)) != UC_ERR_OK) { + printf("not ok %d - uc_mem_read fail for address: 0x%" PRIx64 "\n", log_num++, addr); + } + else { + printf("ok %d - uc_mem_read success after mem_protect at test %d\n", log_num++, test_num - 1); + } + + if (uc_mem_protect(handle, addr & ~0xfffL, 0x1000, UC_PROT_READ | UC_PROT_WRITE) != UC_ERR_OK) { + printf("not ok %d - uc_mem_protect fail during hook_mem_invalid callback, addr: 0x%" PRIx64 "\n", log_num++, addr); + } + else { + printf("ok %d - uc_mem_protect success\n", log_num++); + } + return true; + } +} + +int main(int argc, char **argv, char **envp) +{ + uch handle, trace1, trace2; + uc_err err; + uint32_t addr, testval; + int32_t buf1[1024], buf2[1024], readbuf[1024]; + int i; + + //don't really care about quality of randomness + srand(time(NULL)); + for (i = 0; i < 1024; i++) { + buf1[i] = rand(); + buf2[i] = rand(); + } + + printf("# Memory protect test\n"); + + // Initialize emulator in X86-32bit mode + err = uc_open(UC_ARCH_X86, UC_MODE_32, &handle); + if (err) { + printf("not ok %d - Failed on uc_open() with error returned: %u\n", log_num++, err); + return 1; + } + else { + printf("ok %d - uc_open() success\n", log_num++); + } + + uc_mem_map(handle, CODE_SECTION, CODE_SIZE, UC_PROT_READ | UC_PROT_EXEC); + uc_mem_map(handle, 0x200000, 0x1000, UC_PROT_READ | UC_PROT_WRITE); + uc_mem_map(handle, 0x300000, 0x1000, UC_PROT_READ | UC_PROT_WRITE); + uc_mem_map(handle, 0x3ff000, 0x3000, UC_PROT_READ | UC_PROT_WRITE); + + // fill in sections that shouldn't get touched + if (uc_mem_write(handle, 0x3ff000, (uint8_t*)buf1, 4096)) { + printf("not ok %d - Failed to write random buffer 1 to memory, quit!\n", log_num++); + return 2; + } + else { + printf("ok %d - Random buffer 1 written to memory\n", log_num++); + } + + if (uc_mem_write(handle, 0x401000, (uint8_t*)buf2, 4096)) { + printf("not ok %d - Failed to write random buffer 2 to memory, quit!\n", log_num++); + return 3; + } + else { + printf("ok %d - Random buffer 2 written to memory\n", log_num++); + } + + // write machine code to be emulated to memory + if (uc_mem_write(handle, CODE_SECTION, PROGRAM, sizeof(PROGRAM))) { + printf("not ok %d - Failed to write emulation code to memory, quit!\n", log_num++); + return 4; + } + else { + printf("ok %d - Program written to memory\n", log_num++); + } + + if (uc_hook_add(handle, &trace2, UC_HOOK_CODE, hook_code, NULL, 1, 0) != UC_ERR_OK) { + printf("not ok %d - Failed to install UC_HOOK_CODE handler\n", log_num++); + return 5; + } + else { + printf("ok %d - UC_HOOK_CODE installed\n", log_num++); + } + + // intercept memory write events + if (uc_hook_add(handle, &trace1, UC_HOOK_MEM_WRITE, hook_mem_write, NULL) != UC_ERR_OK) { + printf("not ok %d - Failed to install UC_HOOK_MEM_WRITE handler\n", log_num++); + return 6; + } + else { + printf("ok %d - UC_HOOK_MEM_WRITE installed\n", log_num++); + } + + // intercept invalid memory events + if (uc_hook_add(handle, &trace1, UC_HOOK_MEM_INVALID, hook_mem_invalid, NULL) != UC_ERR_OK) { + printf("not ok %d - Failed to install UC_HOOK_MEM_INVALID handler\n", log_num++); + return 7; + } + else { + printf("ok %d - UC_HOOK_MEM_INVALID installed\n", log_num++); + } + + // emulate machine code until told to stop by hook_code + printf("# BEGIN execution\n"); + err = uc_emu_start(handle, CODE_SECTION, CODE_SECTION + CODE_SIZE, 0, 0); + if (err != UC_ERR_OK) { + printf("not ok %d - Failure on uc_emu_start() with error %u:%s\n", log_num++, err, uc_strerror(err)); + return 8; + } + else { + printf("ok %d - uc_emu_start complete\n", log_num++); + } + printf("# END execution\n"); + + //read from the remapped memory + testval = 0x42424242; + for (addr = 0x200000; addr <= 0x400000; addr += 0x100000) { + uint32_t val; + if (uc_mem_read(handle, addr, (uint8_t*)&val, sizeof(val)) != UC_ERR_OK) { + printf("not ok %d - Failed uc_mem_read for address 0x%x\n", log_num++, addr); + } + else { + printf("ok %d - Good uc_mem_read from 0x%x\n", log_num++, addr); + } + if (val != testval) { + printf("not ok %d - Read 0x%x, expected 0x%x\n", log_num++, val, testval); + } + else { + printf("ok %d - Correct value retrieved\n", log_num++); + } + testval += 0x02020202; + } + + //account for the two mods made by the machine code + buf1[512] = 0x47474747; + buf2[512] = 0x48484848; + + //make sure that random blocks didn't get nuked + // fill in sections that shouldn't get touched + if (uc_mem_read(handle, 0x3ff000, (uint8_t*)readbuf, 4096)) { + printf("not ok %d - Failed to read random buffer 1 from memory\n", log_num++); + } + else { + printf("ok %d - Random buffer 1 read from memory\n", log_num++); + if (memcmp(buf1, readbuf, 4096)) { + printf("not ok %d - Random buffer 1 contents are incorrect\n", log_num++); + } + else { + printf("ok %d - Random buffer 1 contents are correct\n", log_num++); + } + } + + if (uc_mem_read(handle, 0x401000, (uint8_t*)readbuf, 4096)) { + printf("not ok %d - Failed to read random buffer 2 from memory\n", log_num++); + } + else { + printf("ok %d - Random buffer 2 read from memory\n", log_num++); + if (memcmp(buf2, readbuf, 4096)) { + printf("not ok %d - Random buffer 2 contents are incorrect\n", log_num++); + } + else { + printf("ok %d - Random buffer 2 contents are correct\n", log_num++); + } + } + + if (uc_close(&handle) == UC_ERR_OK) { + printf("ok %d - uc_close complete\n", log_num++); + } + else { + printf("not ok %d - uc_close complete\n", log_num++); + } + + return 0; +} diff --git a/samples/mem_unmap.c b/samples/mem_unmap.c new file mode 100644 index 00000000..6f93673d --- /dev/null +++ b/samples/mem_unmap.c @@ -0,0 +1,313 @@ +/* + +uc_mem_unmap demo / unit test + +Copyright(c) 2015 Chris Eagle + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +version 2 as published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +*/ + +#define __STDC_FORMAT_MACROS +#include +#include +#include +#include +#include +#include + +#include + +unsigned char PROGRAM[] = + "\xc7\x05\x00\x00\x20\x00\x41\x41\x41\x41\x90\xc7\x05\x00\x00\x20" + "\x00\x42\x42\x42\x42\xc7\x05\x00\x00\x30\x00\x43\x43\x43\x43\x90" + "\xc7\x05\x00\x00\x30\x00\x44\x44\x44\x44\xc7\x05\x00\x00\x40\x00" + "\x45\x45\x45\x45\x90\xc7\x05\x00\x00\x40\x00\x46\x46\x46\x46\xf4"; +// total size: 64 bytes + +/* +; assumes code section at 0x100000 +; assumes data section at 0x200000, initially rw +; assumes data section at 0x300000, initially rw +; assumes data section at 0x400000, initially rw + +; with installed hooks unmaps or maps on each nop + + mov dword [0x200000], 0x41414141 + nop ; unmap it + mov dword [0x200000], 0x42424242 + + mov dword [0x300000], 0x43434343 + nop ; unmap it + mov dword [0x300000], 0x44444444 + + mov dword [0x400000], 0x45454545 + nop ; unmap it + mov dword [0x400000], 0x46464646 + + hlt ; tell hook function we are done +*/ + +int test_num = 0; +uint32_t tests[] = { + 0x41414141, + 0x43434343, + 0x45454545 +}; + +static int log_num = 1; + +#define CODE_SECTION 0x100000 +#define CODE_SIZE 0x1000 + +// callback for tracing instruction +static void hook_code(uch handle, uint64_t addr, uint32_t size, void *user_data) +{ + uint8_t opcode; + uint32_t testval; + if (uc_mem_read(handle, addr, &opcode, 1) != UC_ERR_OK) { + printf("not ok %d - uc_mem_read fail during hook_code callback, addr: 0x%" PRIx64 "\n", log_num++, addr); + } + printf("ok %d - uc_mem_read for opcode at address 0x%" PRIx64 "\n", log_num++, addr); + switch (opcode) { + case 0x90: //nop + printf("# Handling NOP\n"); + if (uc_mem_read(handle, 0x200000 + test_num * 0x100000, (uint8_t*)&testval, sizeof(testval)) != UC_ERR_OK) { + printf("not ok %d - uc_mem_read fail for address: 0x%x\n", log_num++, 0x200000 + test_num * 0x100000); + } + else { + printf("ok %d - good uc_mem_read for address: 0x%x\n", log_num++, 0x200000 + test_num * 0x100000); + printf("# uc_mem_read for test %d\n", test_num); + + if (testval == tests[test_num]) { + printf("ok %d - passed test %d\n", log_num++, test_num); + } + else { + printf("not ok %d - failed test %d\n", log_num++, test_num); + printf("# Expected: 0x%x\n",tests[test_num]); + printf("# Received: 0x%x\n", testval); + } + } + if (uc_mem_unmap(handle, 0x200000 + test_num * 0x100000, 0x1000) != UC_ERR_OK) { + printf("not ok %d - uc_mem_unmap fail during hook_code callback, addr: 0x%x\n", log_num++, 0x200000 + test_num * 0x100000); + } + else { + printf("ok %d - uc_mem_unmap success\n", log_num++); + } + test_num++; + break; + case 0xf4: //hlt + printf("# Handling HLT\n"); + if (uc_emu_stop(handle) != UC_ERR_OK) { + printf("not ok %d - uc_emu_stop fail during hook_code callback, addr: 0x%" PRIx64 "\n", log_num++, addr); + _exit(-1); + } + else { + printf("ok %d - hlt encountered, uc_emu_stop called\n", log_num++); + } + break; + default: //all others + printf("# Handling OTHER\n"); + break; + } +} + +// callback for tracing memory access (READ or WRITE) +static void hook_mem_write(uch handle, uc_mem_type type, + uint64_t addr, int size, int64_t value, void *user_data) +{ + printf("# write to memory at 0x%"PRIx64 ", data size = %u, data value = 0x%"PRIx64 "\n", addr, size, value); +} + +// callback for tracing invalid memory access (READ or WRITE) +static bool hook_mem_invalid(uch handle, uc_mem_type type, + uint64_t addr, int size, int64_t value, void *user_data) +{ + uint32_t testval; + switch(type) { + default: + printf("not ok %d - UC_HOOK_MEM_INVALID type: %d at 0x%" PRIx64 "\n", log_num++, type, addr); + return false; + case UC_MEM_WRITE: + printf("# write to invalid memory at 0x%"PRIx64 ", data size = %u, data value = 0x%"PRIx64 "\n", addr, size, value); + + if (uc_mem_read(handle, addr, (uint8_t*)&testval, sizeof(testval)) != UC_ERR_OK) { + printf("ok %d - uc_mem_read fail for address: 0x%" PRIx64 "\n", log_num++, addr); + } + else { + printf("not ok %d - uc_mem_read success after unmap at test %d\n", log_num++, test_num - 1); + } + + if (uc_mem_map(handle, addr & ~0xfffL, 0x1000, UC_PROT_READ | UC_PROT_WRITE) != UC_ERR_OK) { + printf("not ok %d - uc_mem_map fail during hook_mem_invalid callback, addr: 0x%" PRIx64 "\n", log_num++, addr); + } + else { + printf("ok %d - uc_mem_map success\n", log_num++); + } + return true; + } +} + +int main(int argc, char **argv, char **envp) +{ + uch handle, trace1, trace2; + uc_err err; + uint32_t addr, testval; + int32_t buf1[1024], buf2[1024], readbuf[1024]; + int i; + + //don't really care about quality of randomness + srand(time(NULL)); + for (i = 0; i < 1024; i++) { + buf1[i] = rand(); + buf2[i] = rand(); + } + + printf("# Memory unmapping test\n"); + + // Initialize emulator in X86-32bit mode + err = uc_open(UC_ARCH_X86, UC_MODE_32, &handle); + if (err) { + printf("not ok %d - Failed on uc_open() with error returned: %u\n", log_num++, err); + return 1; + } + else { + printf("ok %d - uc_open() success\n", log_num++); + } + + uc_mem_map(handle, CODE_SECTION, CODE_SIZE, UC_PROT_READ | UC_PROT_EXEC); + uc_mem_map(handle, 0x200000, 0x1000, UC_PROT_READ | UC_PROT_WRITE); + uc_mem_map(handle, 0x300000, 0x1000, UC_PROT_READ | UC_PROT_WRITE); + uc_mem_map(handle, 0x3ff000, 0x3000, UC_PROT_READ | UC_PROT_WRITE); + + // fill in sections that shouldn't get touched + if (uc_mem_write(handle, 0x3ff000, (uint8_t*)buf1, 4096)) { + printf("not ok %d - Failed to write random buffer 1 to memory, quit!\n", log_num++); + return 2; + } + else { + printf("ok %d - Random buffer 1 written to memory\n", log_num++); + } + + if (uc_mem_write(handle, 0x401000, (uint8_t*)buf2, 4096)) { + printf("not ok %d - Failed to write random buffer 2 to memory, quit!\n", log_num++); + return 3; + } + else { + printf("ok %d - Random buffer 2 written to memory\n", log_num++); + } + + // write machine code to be emulated to memory + if (uc_mem_write(handle, CODE_SECTION, PROGRAM, sizeof(PROGRAM))) { + printf("not ok %d - Failed to write emulation code to memory, quit!\n", log_num++); + return 4; + } + else { + printf("ok %d - Program written to memory\n", log_num++); + } + + if (uc_hook_add(handle, &trace2, UC_HOOK_CODE, hook_code, NULL, 1, 0) != UC_ERR_OK) { + printf("not ok %d - Failed to install UC_HOOK_CODE handler\n", log_num++); + return 5; + } + else { + printf("ok %d - UC_HOOK_CODE installed\n", log_num++); + } + + // intercept memory write events + if (uc_hook_add(handle, &trace1, UC_HOOK_MEM_WRITE, hook_mem_write, NULL) != UC_ERR_OK) { + printf("not ok %d - Failed to install UC_HOOK_MEM_WRITE handler\n", log_num++); + return 6; + } + else { + printf("ok %d - UC_HOOK_MEM_WRITE installed\n", log_num++); + } + + // intercept invalid memory events + if (uc_hook_add(handle, &trace1, UC_HOOK_MEM_INVALID, hook_mem_invalid, NULL) != UC_ERR_OK) { + printf("not ok %d - Failed to install UC_HOOK_MEM_INVALID handler\n", log_num++); + return 7; + } + else { + printf("ok %d - UC_HOOK_MEM_INVALID installed\n", log_num++); + } + + // emulate machine code until told to stop by hook_code + printf("# BEGIN execution\n"); + err = uc_emu_start(handle, CODE_SECTION, CODE_SECTION + CODE_SIZE, 0, 0); + if (err != UC_ERR_OK) { + printf("not ok %d - Failure on uc_emu_start() with error %u:%s\n", log_num++, err, uc_strerror(err)); + return 8; + } + else { + printf("ok %d - uc_emu_start complete\n", log_num++); + } + printf("# END execution\n"); + + //read from the remapped memory + testval = 0x42424242; + for (addr = 0x200000; addr <= 0x400000; addr += 0x100000) { + uint32_t val; + if (uc_mem_read(handle, addr, (uint8_t*)&val, sizeof(val)) != UC_ERR_OK) { + printf("not ok %d - Failed uc_mem_read for address 0x%x\n", log_num++, addr); + } + else { + printf("ok %d - Good uc_mem_read from 0x%x\n", log_num++, addr); + } + if (val != testval) { + printf("not ok %d - Read 0x%x, expected 0x%x\n", log_num++, val, testval); + } + else { + printf("ok %d - Correct value retrieved\n", log_num++); + } + testval += 0x02020202; + } + + //make sure that random blocks didn't get nuked + // fill in sections that shouldn't get touched + if (uc_mem_read(handle, 0x3ff000, (uint8_t*)readbuf, 4096)) { + printf("not ok %d - Failed to read random buffer 1 from memory\n", log_num++); + } + else { + printf("ok %d - Random buffer 1 read from memory\n", log_num++); + if (memcmp(buf1, readbuf, 4096)) { + printf("not ok %d - Random buffer 1 contents are incorrect\n", log_num++); + } + else { + printf("ok %d - Random buffer 1 contents are correct\n", log_num++); + } + } + + if (uc_mem_read(handle, 0x401000, (uint8_t*)readbuf, 4096)) { + printf("not ok %d - Failed to read random buffer 2 from memory\n", log_num++); + } + else { + printf("ok %d - Random buffer 2 read from memory\n", log_num++); + if (memcmp(buf2, readbuf, 4096)) { + printf("not ok %d - Random buffer 2 contents are incorrect\n", log_num++); + } + else { + printf("ok %d - Random buffer 2 contents are correct\n", log_num++); + } + } + + if (uc_close(&handle) == UC_ERR_OK) { + printf("ok %d - uc_close complete\n", log_num++); + } + else { + printf("not ok %d - uc_close complete\n", log_num++); + } + + return 0; +} diff --git a/uc.c b/uc.c index e0e9c655..622de423 100644 --- a/uc.c +++ b/uc.c @@ -31,6 +31,9 @@ #include "qemu/include/hw/boards.h" +static uint8_t *copy_region(uch uc, MemoryRegion *mr); +static bool split_region(uch handle, MemoryRegion *mr, uint64_t address, size_t size, bool do_delete); + UNICORN_EXPORT unsigned int uc_version(unsigned int *major, unsigned int *minor) { @@ -65,8 +68,8 @@ const char *uc_strerror(uc_err code) return "Unknown error code"; case UC_ERR_OK: return "OK (UC_ERR_OK)"; - case UC_ERR_OOM: - return "Out of memory (UC_ERR_OOM)"; + case UC_ERR_NOMEM: + return "No memory available or memory not present (UC_ERR_NOMEM)"; case UC_ERR_ARCH: return "Invalid/unsupported architecture(UC_ERR_ARCH)"; case UC_ERR_HANDLE: @@ -89,10 +92,14 @@ 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_NW: - return "Write to non-writable (UC_ERR_MEM_WRITE_NW)"; - case UC_ERR_MEM_READ_NR: - return "Read from non-readable (UC_ERR_MEM_READ_NR)"; + case UC_ERR_WRITE_PROT: + return "Write to write-protected memory (UC_ERR_WRITE_PROT)"; + case UC_ERR_READ_PROT: + return "Read from non-readable memory (UC_ERR_READ_PROT)"; + case UC_ERR_EXEC_PROT: + return "Fetch from non-executable memory (UC_ERR_EXEC_PROT)"; + case UC_ERR_INVAL: + return "Invalid argumet (UC_ERR_INVAL)"; } } @@ -138,7 +145,7 @@ uc_err uc_open(uc_arch arch, uc_mode mode, uch *handle) uc = calloc(1, sizeof(*uc)); if (!uc) { // memory insufficient - return UC_ERR_OOM; + return UC_ERR_NOMEM; } uc->errnum = UC_ERR_OK; @@ -585,7 +592,7 @@ static int _hook_code(uch handle, int type, uint64_t begin, uint64_t end, i = hook_add(handle, type, begin, end, callback, user_data); if (i == 0) - return UC_ERR_OOM; // FIXME + return UC_ERR_NOMEM; // FIXME *h2 = i; @@ -601,7 +608,7 @@ static uc_err _hook_mem_access(uch handle, uc_hook_t type, i = hook_add(handle, type, begin, end, callback, user_data); if (i == 0) - return UC_ERR_OOM; // FIXME + return UC_ERR_NOMEM; // FIXME *h2 = i; @@ -620,24 +627,24 @@ uc_err uc_mem_map(uch handle, uint64_t address, size_t size, uint32_t perms) if (size == 0) // invalid memory mapping - return UC_ERR_MAP; + return UC_ERR_INVAL; - // address must be aligned to 4KB - if ((address & (4*1024 - 1)) != 0) - return UC_ERR_MAP; + // address must be aligned to uc->target_page_size + if ((address & uc->target_page_align) != 0) + return UC_ERR_INVAL; - // size must be multiple of 4KB - if ((size & (4*1024 - 1)) != 0) - return UC_ERR_MAP; + // size must be multiple of uc->target_page_size + if ((size & uc->target_page_align) != 0) + return UC_ERR_INVAL; // check for only valid permissions - if ((perms & ~(UC_PROT_READ | UC_PROT_WRITE)) != 0) - return UC_ERR_MAP; + if ((perms & ~UC_PROT_ALL) != 0) + return UC_ERR_INVAL; if ((uc->mapped_block_count & (MEM_BLOCK_INCR - 1)) == 0) { //time to grow regions = (MemoryRegion**)realloc(uc->mapped_blocks, sizeof(MemoryRegion*) * (uc->mapped_block_count + MEM_BLOCK_INCR)); if (regions == NULL) { - return UC_ERR_OOM; + return UC_ERR_NOMEM; } uc->mapped_blocks = regions; } @@ -647,6 +654,228 @@ uc_err uc_mem_map(uch handle, uint64_t address, size_t size, uint32_t perms) return UC_ERR_OK; } +//create a backup copy of the indicated MemoryRegion +//generally used in prepartion for splitting a MemoryRegion +static uint8_t *copy_region(uch handle, MemoryRegion *mr) +{ + uint8_t *block = (uint8_t *)malloc(int128_get64(mr->size)); + if (block != NULL) { + uc_err err = uc_mem_read(handle, mr->addr, block, int128_get64(mr->size)); + if (err != UC_ERR_OK) { + free(block); + block = NULL; + } + } + return block; +} + +/* +Split the given MemoryRegion at the indicated address for the indicated size +this may result in the create of up to 3 spanning sections. If the delete +parameter is true, the no new section will be created to replace the indicate +range. This functions exists to support uc_mem_protect and uc_mem_unmap. + +This is a static function and callers have already done some preliminary +parameter validation. +*/ +//TODO: investigate whether qemu region manipulation functions already offer this capability +static bool split_region(uch handle, MemoryRegion *mr, uint64_t address, size_t size, bool do_delete) +{ + uint8_t *backup; + uint32_t perms; + uint64_t begin, end, chunk_end; + size_t l_size, m_size, r_size; + chunk_end = address + size; + if (address <= mr->addr && chunk_end >= mr->end) { + //trivial case, if we are deleting, just unmap + if (do_delete) + return uc_mem_unmap(handle, mr->addr, int128_get64(mr->size)) == UC_ERR_OK; + return true; + } + + if (size == 0) + //trivial case + return true; + + if (address >= mr->end || chunk_end <= mr->addr) + //impossible case + return false; + + backup = copy_region(handle, mr); + if (backup == NULL) + return false; + + //save the essential information required for the split before mr gets deleted + perms = mr->perms; + begin = mr->addr; + end = mr->end; + + if (uc_mem_unmap(handle, mr->addr, int128_get64(mr->size)) != UC_ERR_OK) + goto error; + + /* overlapping cases + * |------mr------| + * case 1 |---size--| + * case 2 |--size--| + * case 3 |---size--| + */ + + //adjust some things + if (address < begin) + address = begin; + if (chunk_end > end) + chunk_end = end; + + //compute sub region sizes + l_size = (size_t)(address - begin); + r_size = (size_t)(end - chunk_end); + m_size = (size_t)(chunk_end - address); + + //If there are error in any of the below operations, things are too far gone + //at that point to recover. Could try to remap orignal region, but these smaller + //allocation just failed so no guarantee that we can recover the original + //allocation at this point + if (l_size > 0) { + if (uc_mem_map(handle, begin, l_size, perms) != UC_ERR_OK) + goto error; + if (uc_mem_write(handle, begin, backup, l_size) != UC_ERR_OK) + goto error; + } + if (m_size > 0 && !do_delete) { + if (uc_mem_map(handle, address, m_size, perms) != UC_ERR_OK) + goto error; + if (uc_mem_write(handle, address, backup + l_size, m_size) != UC_ERR_OK) + goto error; + } + if (r_size > 0) { + if (uc_mem_map(handle, chunk_end, r_size, perms) != UC_ERR_OK) + goto error; + if (uc_mem_write(handle, chunk_end, backup + l_size + m_size, r_size) != UC_ERR_OK) + goto error; + } + return true; +error: + free(backup); + return false; +} + +UNICORN_EXPORT +uc_err uc_mem_protect(uch handle, uint64_t address, size_t size, uint32_t perms) +{ + struct uc_struct* uc = (struct uc_struct *)handle; + MemoryRegion *mr; + + if (handle == 0) + // invalid handle + return UC_ERR_UCH; + + if (size == 0) + // trivial case, no change + return UC_ERR_OK; + + // address must be aligned to uc->target_page_size + if ((address & uc->target_page_align) != 0) + return UC_ERR_INVAL; + + // size must be multiple of uc->target_page_size + if ((size & uc->target_page_align) != 0) + return UC_ERR_INVAL; + + // check for only valid permissions + if ((perms & ~UC_PROT_ALL) != 0) + return UC_ERR_INVAL; + + //check that user's entire requested block is mapped + if (!check_mem_area(uc, address, size)) + return UC_ERR_NOMEM; + + //Now we know entire region is mapped, so change permissions + //If request exactly matches a region we don't need to split + mr = memory_mapping(uc, address); + if (address != mr->addr || size != int128_get64(mr->size)) { + //ouch, we are going to need to subdivide blocks + uint64_t addr = address; + size_t count = 0, len; + while(count < size) { + MemoryRegion *mr = memory_mapping(uc, addr); + len = MIN(size - count, mr->end - addr); + if (!split_region(handle, mr, addr, len, false)) + return UC_ERR_NOMEM; + count += len; + addr += len; + } + //Grab a pointer to the newly split MemoryRegion + mr = memory_mapping(uc, address); + if (mr == NULL) { + //this should never happern if splitting succeeded + return UC_ERR_NOMEM; + } + } + //regions exactly matches an existing region just change perms + mr->perms = perms; + uc->readonly_mem(mr, (perms & UC_PROT_WRITE) == 0); + + return UC_ERR_OK; +} + +UNICORN_EXPORT +uc_err uc_mem_unmap(uch handle, uint64_t address, size_t size) +{ + MemoryRegion *mr; + unsigned int i; + struct uc_struct* uc = (struct uc_struct *)handle; + + if (handle == 0) + // invalid handle + return UC_ERR_UCH; + + if (size == 0) + // nothing to unmap + return UC_ERR_OK; + + // address must be aligned to uc->target_page_size + if ((address & uc->target_page_align) != 0) + return UC_ERR_INVAL; + + // size must be multiple of uc->target_page_size + if ((size & uc->target_page_align) != 0) + return UC_ERR_MAP; + + //check that user's entire requested block is mapped + if (!check_mem_area(uc, address, size)) + return UC_ERR_NOMEM; + + //Now we know entire region is mapped, so begin the delete + //check trivial case first + mr = memory_mapping(uc, address); + if (address == mr->addr && size == int128_get64(mr->size)) { + //regions exactly matches an existing region just unmap it + //this termiantes a possible recursion between this function and split_region + uc->memory_unmap(uc, mr); + for (i = 0; i < uc->mapped_block_count; i++) { + if (uc->mapped_blocks[i] == mr) { + uc->mapped_block_count--; + //shift remainder of array down over deleted pointer + memcpy(&uc->mapped_blocks[i], &uc->mapped_blocks[i + 1], sizeof(MemoryRegion*) * (uc->mapped_block_count - i)); + break; + } + } + } + else { + //ouch, we are going to need to subdivide blocks + size_t count = 0, len; + while(count < size) { + MemoryRegion *mr = memory_mapping(uc, address); + len = MIN(size - count, mr->end - address); + if (!split_region(handle, mr, address, len, true)) + return UC_ERR_NOMEM; + count += len; + address += len; + } + } + return UC_ERR_OK; +} + MemoryRegion *memory_mapping(struct uc_struct* uc, uint64_t address) { unsigned int i; @@ -675,7 +904,7 @@ static uc_err _hook_mem_invalid(struct uc_struct* uc, uc_cb_eventmem_t callback, uc->hook_mem_idx = i; return UC_ERR_OK; } else - return UC_ERR_OOM; + return UC_ERR_NOMEM; } @@ -694,7 +923,7 @@ static uc_err _hook_intr(struct uc_struct* uc, void *callback, uc->hook_intr_idx = i; return UC_ERR_OK; } else - return UC_ERR_OOM; + return UC_ERR_NOMEM; } @@ -718,7 +947,7 @@ static uc_err _hook_insn(struct uc_struct *uc, unsigned int insn_id, void *callb uc->hook_out_idx = i; return UC_ERR_OK; } else - return UC_ERR_OOM; + return UC_ERR_NOMEM; case UC_X86_INS_IN: // FIXME: only one event handler at the same time i = hook_find_new(uc); @@ -729,7 +958,7 @@ static uc_err _hook_insn(struct uc_struct *uc, unsigned int insn_id, void *callb uc->hook_in_idx = i; return UC_ERR_OK; } else - return UC_ERR_OOM; + return UC_ERR_NOMEM; case UC_X86_INS_SYSCALL: case UC_X86_INS_SYSENTER: // FIXME: only one event handler at the same time @@ -741,7 +970,7 @@ static uc_err _hook_insn(struct uc_struct *uc, unsigned int insn_id, void *callb uc->hook_syscall_idx = i; return UC_ERR_OK; } else - return UC_ERR_OOM; + return UC_ERR_NOMEM; } break; }