From 00944b6cdef70eda67f8b3ea5fae0e57c24b2f53 Mon Sep 17 00:00:00 2001 From: Chris Eagle Date: Wed, 26 Aug 2015 13:29:54 -0700 Subject: [PATCH 01/14] Add ability to mark memory are read only. Add new API uc_mem_map_ex to allow permissions to be passed. Change MemoryBlock to track created MemoryRegions. Add regress/ro_mem_test.c --- include/uc_priv.h | 7 +++-- include/unicorn/unicorn.h | 22 ++++++++++++- qemu/include/exec/memory.h | 4 ++- qemu/memory.c | 10 ++++-- qemu/unicorn_common.h | 1 + regress/ro_mem_test.c | 64 ++++++++++++++++++++++++++++++++++++++ uc.c | 43 ++++++++++++++++++++++--- 7 files changed, 139 insertions(+), 12 deletions(-) mode change 100644 => 100755 qemu/include/exec/memory.h mode change 100644 => 100755 qemu/memory.c mode change 100644 => 100755 qemu/unicorn_common.h create mode 100644 regress/ro_mem_test.c diff --git a/include/uc_priv.h b/include/uc_priv.h index 3fde4862..e9ebb23f 100755 --- a/include/uc_priv.h +++ b/include/uc_priv.h @@ -16,7 +16,7 @@ QTAILQ_HEAD(CPUTailQ, CPUState); typedef struct MemoryBlock { - uint64_t begin; //inclusive + MemoryRegion *region; //inclusive uint64_t end; //exclusive uint32_t perms; } MemoryBlock; @@ -51,7 +51,9 @@ typedef void (*uc_args_uc_long_t)(struct uc_struct*, unsigned long); typedef void (*uc_args_uc_u64_t)(struct uc_struct *, uint64_t addr); -typedef int (*uc_args_uc_ram_size_t)(struct uc_struct*, ram_addr_t begin, size_t size); +typedef MemoryRegion* (*uc_args_uc_ram_size_t)(struct uc_struct*, ram_addr_t begin, size_t size, uint32_t perms); + +typedef void (*uc_readonly_mem_t)(MemoryRegion *mr, bool readonly); // which interrupt should make emulation stop? typedef bool (*uc_args_int_t)(int intno); @@ -94,6 +96,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_readonly_mem_t readonly_mem; // list of cpu void* cpu; diff --git a/include/unicorn/unicorn.h b/include/unicorn/unicorn.h index ebdb47f3..975581cc 100755 --- a/include/unicorn/unicorn.h +++ b/include/unicorn/unicorn.h @@ -392,7 +392,8 @@ typedef enum 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. @@ -406,6 +407,25 @@ typedef enum uc_prot { UNICORN_EXPORT uc_err uc_mem_map(uch handle, uint64_t address, size_t size); +/* + Map memory in for emulation. + This API adds a memory region that can be used by emulation. + + @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. + @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. + @perms: Permissions for the newly mapped region. + This must be some combination of UC_PROT_READ | UC_PROT_WRITE | UC_PROT_EXEC, + or this will return with UC_ERR_MAP 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_ex(uch handle, uint64_t address, size_t size, uint32_t perms); + #ifdef __cplusplus } #endif diff --git a/qemu/include/exec/memory.h b/qemu/include/exec/memory.h old mode 100644 new mode 100755 index c8b0c333..81081405 --- a/qemu/include/exec/memory.h +++ b/qemu/include/exec/memory.h @@ -315,12 +315,14 @@ void memory_region_init_io(struct uc_struct *uc, MemoryRegion *mr, * @owner: the object that tracks the region's reference count * @name: the name of the region. * @size: size of the region. + * @perms: permissions on the region (UC_PROT_READ, UC_PROT_WRITE, UC_PROT_EXEC). * @errp: pointer to Error*, to store an error if it happens. */ void memory_region_init_ram(struct uc_struct *uc, MemoryRegion *mr, struct Object *owner, const char *name, uint64_t size, + uint32_t perms, Error **errp); /** @@ -934,7 +936,7 @@ void address_space_unmap(AddressSpace *as, void *buffer, hwaddr len, void memory_register_types(struct uc_struct *uc); -int memory_map(struct uc_struct *uc, ram_addr_t begin, size_t size); +MemoryRegion *memory_map(struct uc_struct *uc, ram_addr_t begin, size_t size, uint32_t perms); int memory_free(struct uc_struct *uc); #endif diff --git a/qemu/memory.c b/qemu/memory.c old mode 100644 new mode 100755 index f7bf161b..74ea2cf8 --- a/qemu/memory.c +++ b/qemu/memory.c @@ -31,18 +31,18 @@ // Unicorn engine -int memory_map(struct uc_struct *uc, ram_addr_t begin, size_t size) +MemoryRegion *memory_map(struct uc_struct *uc, ram_addr_t begin, size_t size, uint32_t perms) { uc->ram = g_new(MemoryRegion, 1); - memory_region_init_ram(uc, uc->ram, NULL, "pc.ram", size, &error_abort); + memory_region_init_ram(uc, uc->ram, NULL, "pc.ram", size, perms, &error_abort); memory_region_add_subregion(get_system_memory(uc), begin, uc->ram); if (uc->current_cpu) tlb_flush(uc->current_cpu, 1); - return 0; + return uc->ram; } int memory_free(struct uc_struct *uc) @@ -1151,10 +1151,14 @@ void memory_region_init_ram(struct uc_struct *uc, MemoryRegion *mr, Object *owner, const char *name, uint64_t size, + uint32_t perms, Error **errp) { memory_region_init(uc, mr, owner, name, size); mr->ram = true; + if (!(perms & UC_PROT_WRITE)) { + mr->readonly = true; + } mr->terminates = true; mr->destructor = memory_region_destructor_ram; mr->ram_addr = qemu_ram_alloc(size, mr, errp); diff --git a/qemu/unicorn_common.h b/qemu/unicorn_common.h old mode 100644 new mode 100755 index 93f1c5f4..176900cb --- a/qemu/unicorn_common.h +++ b/qemu/unicorn_common.h @@ -73,6 +73,7 @@ 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->readonly_mem = memory_region_set_readonly; if (!uc->release) uc->release = release_common; diff --git a/regress/ro_mem_test.c b/regress/ro_mem_test.c new file mode 100644 index 00000000..57f4f0ca --- /dev/null +++ b/regress/ro_mem_test.c @@ -0,0 +1,64 @@ +#include +#include +#include + +#include + +#define PROGRAM "\xeb\x08\x58\xc7\x00\x78\x56\x34\x12\x90\xe8\xf3\xff\xff\xff" + +/* +bits 32 + + jmp short bottom +top: + pop eax + mov dword [eax], 0x12345678 + nop +bottom: + call top +*/ + +int main(int argc, char **argv, char **envp) { + uch handle; + uc_err err; + uint8_t bytes[8]; + + printf("Memory mapping test\n"); + + // Initialize emulator in X86-32bit mode + err = uc_open(UC_ARCH_X86, UC_MODE_32, &handle); + if (err) { + printf("Failed on uc_open() with error returned: %u\n", err); + return; + } + + uc_mem_map(handle, 0x100000, 0x1000); + uc_mem_map(handle, 0x200000, 0x2000); + uc_mem_map(handle, 0x300000, 0x3000); + uc_mem_map_ex(handle, 0x400000, 0x4000, UC_PROT_READ | UC_PROT_EXEC); + + // write machine code to be emulated to memory + if (uc_mem_write(handle, 0x400000, PROGRAM, sizeof(PROGRAM))) { + printf("Failed to write emulation code to memory, quit!\n"); + return; + } + else { + printf("Allowed to write to read only memory via uc_mem_write\n"); + } + + // emulate machine code in infinite time + printf("BEGIN execution\n"); + err = uc_emu_start(handle, 0x400000, 0x400000 + sizeof(PROGRAM), 0, 5); + if (err) { + printf("Failed on uc_emu_start() with error returned %u: %s\n", + err, uc_strerror(err)); + } + printf("END execution\n"); + + if (!uc_mem_read(handle, 0x400000 + sizeof(PROGRAM) - 1, bytes, 4)) + printf(">>> Read 4 bytes from [0x%x] = 0x%x\n", 0x400000 + sizeof(PROGRAM) - 1,*(uint32_t*) bytes); + else + printf(">>> Failed to read 4 bytes from [0x%x]\n", 0x400000 + sizeof(PROGRAM) - 1); + + uc_close(&handle); +} diff --git a/uc.c b/uc.c index a7533196..5c5564cc 100755 --- a/uc.c +++ b/uc.c @@ -350,7 +350,18 @@ uc_err uc_mem_read(uch handle, uint64_t address, uint8_t *bytes, size_t size) return UC_ERR_OK; } +static const MemoryBlock *getMemoryBlock(struct uc_struct *uc, uint64_t address); +static const MemoryBlock *getMemoryBlock(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 &uc->mapped_blocks[i]; + } + + // not found + return NULL; +} UNICORN_EXPORT uc_err uc_mem_write(uch handle, uint64_t address, const uint8_t *bytes, size_t size) @@ -361,9 +372,21 @@ uc_err uc_mem_write(uch handle, uint64_t address, const uint8_t *bytes, size_t s // invalid handle return UC_ERR_UCH; + const MemoryBlock *mb = getMemoryBlock(uc, address); + if (mb == NULL) + return UC_ERR_MEM_WRITE; + + if (!(mb->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 + //now write protect it again + uc->readonly_mem(mb->region, true); + return UC_ERR_OK; } @@ -529,7 +552,7 @@ static uc_err _hook_mem_access(uch handle, uc_mem_type type, } UNICORN_EXPORT -uc_err uc_mem_map(uch handle, uint64_t address, size_t size) +uc_err uc_mem_map_ex(uch handle, uint64_t address, size_t size, uint32_t perms) { MemoryBlock *blocks; struct uc_struct* uc = (struct uc_struct *)handle; @@ -550,6 +573,10 @@ uc_err uc_mem_map(uch handle, uint64_t address, size_t size) if ((size & (4*1024 - 1)) != 0) return UC_ERR_MAP; + // check for only valid permissions + if ((perms & ~(UC_PROT_READ | UC_PROT_WRITE | UC_PROT_EXEC)) != 0) + return UC_ERR_MAP; + if ((uc->mapped_block_count & (MEM_BLOCK_INCR - 1)) == 0) { //time to grow blocks = realloc(uc->mapped_blocks, sizeof(MemoryBlock) * (uc->mapped_block_count + MEM_BLOCK_INCR)); if (blocks == NULL) { @@ -557,22 +584,28 @@ uc_err uc_mem_map(uch handle, uint64_t address, size_t size) } uc->mapped_blocks = blocks; } - uc->mapped_blocks[uc->mapped_block_count].begin = address; 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 = UC_PROT_READ | UC_PROT_WRITE | UC_PROT_EXEC; - uc->memory_map(uc, address, size); + 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++; return UC_ERR_OK; } +UNICORN_EXPORT +uc_err uc_mem_map(uch handle, uint64_t address, size_t size) +{ + //old api, maps RWX by default + 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) { unsigned int i; for(i = 0; i < uc->mapped_block_count; i++) { - if (address >= uc->mapped_blocks[i].begin && address < uc->mapped_blocks[i].end) + if (address >= uc->mapped_blocks[i].region->addr && address < uc->mapped_blocks[i].end) return true; } From 4b529bc56cc823f1f2d0cced3398c76ed399e903 Mon Sep 17 00:00:00 2001 From: Chris Eagle Date: Wed, 26 Aug 2015 14:09:46 -0700 Subject: [PATCH 02/14] Free up all MemoryRegion* when uc is closed --- qemu/memory.c | 9 +++++---- regress/Makefile | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/qemu/memory.c b/qemu/memory.c index 74ea2cf8..8c5fd883 100755 --- a/qemu/memory.c +++ b/qemu/memory.c @@ -47,11 +47,12 @@ MemoryRegion *memory_map(struct uc_struct *uc, ram_addr_t begin, size_t size, ui int memory_free(struct uc_struct *uc) { + int i; get_system_memory(uc)->enabled = false; - if (uc->ram) { - uc->ram->enabled = false; - memory_region_del_subregion(get_system_memory(uc), uc->ram); - g_free(uc->ram); + for (i = 0; i < uc->mapped_block_count; i++) { + uc->mapped_blocks[i].region->enabled = false; + memory_region_del_subregion(get_system_memory(uc), uc->mapped_blocks[i].region); + g_free(uc->mapped_blocks[i].region); } return 0; } diff --git a/regress/Makefile b/regress/Makefile index 06c5a31a..fccfa32c 100644 --- a/regress/Makefile +++ b/regress/Makefile @@ -1,7 +1,7 @@ CFLAGS += -I../include LDFLAGS = -L.. -lunicorn -TESTS = map_crash sigill sigill2 block_test +TESTS = map_crash sigill sigill2 block_test ro_mem_test all: $(TESTS) From dec793e98412095acebf60a10c0697aabb9f155a Mon Sep 17 00:00:00 2001 From: Chris Eagle Date: Wed, 26 Aug 2015 21:28:36 -0700 Subject: [PATCH 03/14] setup stack pointer in ro_mem_test --- regress/ro_mem_test.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) mode change 100644 => 100755 regress/ro_mem_test.c diff --git a/regress/ro_mem_test.c b/regress/ro_mem_test.c old mode 100644 new mode 100755 index 57f4f0ca..e04aaf6d --- a/regress/ro_mem_test.c +++ b/regress/ro_mem_test.c @@ -18,10 +18,25 @@ bottom: call top */ +// callback for tracing instruction +static void hook_code(uch handle, uint64_t address, uint32_t size, void *user_data) +{ + uint32_t esp; + printf(">>> Tracing instruction at 0x%"PRIx64 ", instruction size = 0x%x\n", address, size); + + uc_reg_read(handle, UC_X86_REG_ESP, &esp); + printf(">>> --- ESP is 0x%x\n", esp); + +} + +#define STACK 0x500000 +#define STACK_SIZE 0x5000 + int main(int argc, char **argv, char **envp) { - uch handle; + uch handle, trace2; uc_err err; uint8_t bytes[8]; + uint32_t esp; printf("Memory mapping test\n"); @@ -36,6 +51,11 @@ int main(int argc, char **argv, char **envp) { uc_mem_map(handle, 0x200000, 0x2000); uc_mem_map(handle, 0x300000, 0x3000); uc_mem_map_ex(handle, 0x400000, 0x4000, UC_PROT_READ | UC_PROT_EXEC); + uc_mem_map_ex(handle, STACK, STACK_SIZE, UC_PROT_READ | UC_PROT_WRITE); + + esp = STACK + STACK_SIZE; + + uc_reg_write(handle, UC_X86_REG_ESP, &esp); // write machine code to be emulated to memory if (uc_mem_write(handle, 0x400000, PROGRAM, sizeof(PROGRAM))) { @@ -46,6 +66,8 @@ int main(int argc, char **argv, char **envp) { printf("Allowed to write to read only memory via uc_mem_write\n"); } + //uc_hook_add(handle, &trace2, UC_HOOK_CODE, hook_code, NULL, (uint64_t)0x400000, (uint64_t)0x400fff); + // emulate machine code in infinite time printf("BEGIN execution\n"); err = uc_emu_start(handle, 0x400000, 0x400000 + sizeof(PROGRAM), 0, 5); From a5cc88d00d0cd5d9a910ec94df379589b558fb80 Mon Sep 17 00:00:00 2001 From: Chris Eagle Date: Wed, 26 Aug 2015 21:47:15 -0700 Subject: [PATCH 04/14] Eliminate clang warnings in ro_mem_test.c --- regress/ro_mem_test.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/regress/ro_mem_test.c b/regress/ro_mem_test.c index e04aaf6d..4cd102f8 100755 --- a/regress/ro_mem_test.c +++ b/regress/ro_mem_test.c @@ -4,7 +4,7 @@ #include -#define PROGRAM "\xeb\x08\x58\xc7\x00\x78\x56\x34\x12\x90\xe8\xf3\xff\xff\xff" +const uint8_t PROGRAM[] = "\xeb\x08\x58\xc7\x00\x78\x56\x34\x12\x90\xe8\xf3\xff\xff\xff"; /* bits 32 @@ -37,6 +37,7 @@ int main(int argc, char **argv, char **envp) { uc_err err; uint8_t bytes[8]; uint32_t esp; + int result; printf("Memory mapping test\n"); @@ -44,7 +45,7 @@ int main(int argc, char **argv, char **envp) { err = uc_open(UC_ARCH_X86, UC_MODE_32, &handle); if (err) { printf("Failed on uc_open() with error returned: %u\n", err); - return; + return 1; } uc_mem_map(handle, 0x100000, 0x1000); @@ -60,7 +61,7 @@ int main(int argc, char **argv, char **envp) { // write machine code to be emulated to memory if (uc_mem_write(handle, 0x400000, PROGRAM, sizeof(PROGRAM))) { printf("Failed to write emulation code to memory, quit!\n"); - return; + return 2; } else { printf("Allowed to write to read only memory via uc_mem_write\n"); @@ -74,13 +75,19 @@ int main(int argc, char **argv, char **envp) { if (err) { printf("Failed on uc_emu_start() with error returned %u: %s\n", err, uc_strerror(err)); + return 3; } printf("END execution\n"); - if (!uc_mem_read(handle, 0x400000 + sizeof(PROGRAM) - 1, bytes, 4)) - printf(">>> Read 4 bytes from [0x%x] = 0x%x\n", 0x400000 + sizeof(PROGRAM) - 1,*(uint32_t*) bytes); - else - printf(">>> Failed to read 4 bytes from [0x%x]\n", 0x400000 + sizeof(PROGRAM) - 1); + if (!uc_mem_read(handle, 0x400000 + sizeof(PROGRAM) - 1, bytes, 4)) { + printf(">>> Read 4 bytes from [0x%x] = 0x%x\n", (uint32_t)(0x400000 + sizeof(PROGRAM) - 1),*(uint32_t*) bytes); + } + else { + printf(">>> Failed to read 4 bytes from [0x%x]\n", (uint32_t)(0x400000 + sizeof(PROGRAM) - 1)); + return 4; + } uc_close(&handle); + + return 0; } From 980ec8b08736e51fe9100abb46ec1d1aecab0bc9 Mon Sep 17 00:00:00 2001 From: Chris Eagle Date: Wed, 26 Aug 2015 23:21:41 -0700 Subject: [PATCH 05/14] Demonstrate continued correct behavior on invalid memory access --- regress/ro_mem_test.c | 58 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/regress/ro_mem_test.c b/regress/ro_mem_test.c index 4cd102f8..9a2f01f9 100755 --- a/regress/ro_mem_test.c +++ b/regress/ro_mem_test.c @@ -29,18 +29,59 @@ static void hook_code(uch handle, uint64_t address, uint32_t size, void *user_da } +// callback for tracing memory access (READ or WRITE) +static bool hook_mem_invalid(uch handle, uc_mem_type type, + uint64_t address, int size, int64_t value, void *user_data) +{ + uint32_t esp; + uc_reg_read(handle, UC_X86_REG_ESP, &esp); + + switch(type) { + default: + // return false to indicate we want to stop emulation + return false; + case UC_MEM_WRITE: + //if this is a push, esp has not been adjusted yet + if (esp == (address + size)) { + uint32_t upper; + 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); + // 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", + address, size, value); + return false; + } +} + + #define STACK 0x500000 #define STACK_SIZE 0x5000 int main(int argc, char **argv, char **envp) { - uch handle, trace2; + uch handle, trace1, trace2; uc_err err; uint8_t bytes[8]; uint32_t esp; int result; + int map_stack = 0; + + if (argc == 2 && strcmp(argv[1], "--map-stack") == 0) { + map_stack = 1; + } 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) { @@ -52,7 +93,10 @@ int main(int argc, char **argv, char **envp) { uc_mem_map(handle, 0x200000, 0x2000); uc_mem_map(handle, 0x300000, 0x3000); uc_mem_map_ex(handle, 0x400000, 0x4000, UC_PROT_READ | UC_PROT_EXEC); - uc_mem_map_ex(handle, STACK, STACK_SIZE, UC_PROT_READ | UC_PROT_WRITE); + + if (map_stack) { + uc_mem_map_ex(handle, STACK, STACK_SIZE, UC_PROT_READ | UC_PROT_WRITE); + } esp = STACK + STACK_SIZE; @@ -69,6 +113,9 @@ int main(int argc, char **argv, char **envp) { //uc_hook_add(handle, &trace2, UC_HOOK_CODE, hook_code, NULL, (uint64_t)0x400000, (uint64_t)0x400fff); + // intercept invalid memory events + 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); @@ -79,11 +126,12 @@ int main(int argc, char **argv, char **envp) { } printf("END execution\n"); - if (!uc_mem_read(handle, 0x400000 + sizeof(PROGRAM) - 1, bytes, 4)) { - printf(">>> Read 4 bytes from [0x%x] = 0x%x\n", (uint32_t)(0x400000 + sizeof(PROGRAM) - 1),*(uint32_t*) bytes); + 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); } else { - printf(">>> Failed to read 4 bytes from [0x%x]\n", (uint32_t)(0x400000 + sizeof(PROGRAM) - 1)); + printf(">>> Failed to read 4 bytes from [0x%x]\n", (uint32_t)(esp - 4)); return 4; } From f357f4de21aced7b8369c9335e9b5c1ff181edfb Mon Sep 17 00:00:00 2001 From: Chris Eagle Date: Wed, 26 Aug 2015 23:35:23 -0700 Subject: [PATCH 06/14] Improve status reporting in regress/ro_mem_test.c --- regress/ro_mem_test.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/regress/ro_mem_test.c b/regress/ro_mem_test.c index 9a2f01f9..de7a8356 100755 --- a/regress/ro_mem_test.c +++ b/regress/ro_mem_test.c @@ -4,7 +4,7 @@ #include -const uint8_t PROGRAM[] = "\xeb\x08\x58\xc7\x00\x78\x56\x34\x12\x90\xe8\xf3\xff\xff\xff"; +const uint8_t PROGRAM[] = "\xeb\x08\x58\xc7\x00\x78\x56\x34\x12\x90\xe8\xf3\xff\xff\xff\x41\x41\x41\x41"; /* bits 32 @@ -124,8 +124,26 @@ int main(int argc, char **argv, char **envp) { err, uc_strerror(err)); return 3; } + else { + printf("uc_emu_start returned UC_ERR_OK\n"); + } printf("END execution\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); + if (0x41414141 != *(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); From 686acb7e6e96f30876c18f179926e8ca907477d4 Mon Sep 17 00:00:00 2001 From: Chris Eagle Date: Thu, 27 Aug 2015 18:03:17 -0700 Subject: [PATCH 07/14] Detect all occurences of write to read only page. Add callback capability on write to read only. Add new error type UC_ERR_MEM_WRITE_RO and new access type UC_MEM_WRITE_RO for use in callback --- hook.c | 0 include/hook.h | 0 include/uc_priv.h | 5 +-- include/unicorn/unicorn.h | 2 + qemu/include/exec/memory.h | 1 + qemu/memory.c | 1 + qemu/softmmu_template.h | 81 ++++++++++++++++++++++++++++++-------- regress/ro_mem_test.c | 80 ++++++++++++++++++++++++++----------- uc.c | 14 +++---- 9 files changed, 136 insertions(+), 48 deletions(-) mode change 100644 => 100755 hook.c mode change 100644 => 100755 include/hook.h 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); } - From 140e9f9ae2b08a2f36f5ff05c8044defb248a16a Mon Sep 17 00:00:00 2001 From: Chris Eagle Date: Thu, 27 Aug 2015 18:04:05 -0700 Subject: [PATCH 08/14] fix perms on files --- hook.c | 0 include/hook.h | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 hook.c mode change 100755 => 100644 include/hook.h diff --git a/hook.c b/hook.c old mode 100755 new mode 100644 diff --git a/include/hook.h b/include/hook.h old mode 100755 new mode 100644 From 9530b2daff2f46be0ee6c1ac8561da262b7e4db9 Mon Sep 17 00:00:00 2001 From: Chris Eagle Date: Thu, 27 Aug 2015 23:19:32 -0700 Subject: [PATCH 09/14] Remove MemoryBlock struct by consolidating in MemoryRegion. add new API uc_mem_protect. Add regress/mem_protect.c. Drop UC_PROT_EXEC for time being --- include/uc_priv.h | 7 +- include/unicorn/unicorn.h | 24 +++- qemu/include/exec/memory.h | 1 + qemu/memory.c | 13 +- regress/Makefile | 2 +- regress/mem_protect.c | 237 +++++++++++++++++++++++++++++++++++++ regress/ro_mem_test.c | 2 +- uc.c | 129 ++++++++++++++++---- 8 files changed, 379 insertions(+), 36 deletions(-) create mode 100755 regress/mem_protect.c diff --git a/include/uc_priv.h b/include/uc_priv.h index a8d601be..686ab9e5 100755 --- a/include/uc_priv.h +++ b/include/uc_priv.h @@ -15,11 +15,6 @@ QTAILQ_HEAD(CPUTailQ, CPUState); -typedef struct MemoryBlock { - MemoryRegion *region; //inclusive begin - uint64_t end; //exclusive -} MemoryBlock; - typedef struct ModuleEntry { void (*init)(void); QTAILQ_ENTRY(ModuleEntry) node; @@ -176,7 +171,7 @@ struct uc_struct { int thumb; // thumb mode for ARM // full TCG cache leads to middle-block break in the last translation? bool block_full; - MemoryBlock *mapped_blocks; + MemoryRegion **mapped_blocks; uint32_t mapped_block_count; }; diff --git a/include/unicorn/unicorn.h b/include/unicorn/unicorn.h index 862382a8..dd4acbee 100755 --- a/include/unicorn/unicorn.h +++ b/include/unicorn/unicorn.h @@ -389,13 +389,12 @@ uc_err uc_hook_del(uch handle, uch *h2); typedef enum uc_prot { UC_PROT_READ = 1, UC_PROT_WRITE = 2, - UC_PROT_EXEC = 4 } uc_prot; /* Map memory in for 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. + with permissions UC_PROT_READ | UC_PROT_WRITE. @handle: handle returned by uc_open() @address: starting address of the new memory region to be mapped in. @@ -419,7 +418,7 @@ uc_err uc_mem_map(uch handle, uint64_t address, size_t size); @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. @perms: Permissions for the newly mapped region. - This must be some combination of UC_PROT_READ | UC_PROT_WRITE | UC_PROT_EXEC, + This must be some combination of UC_PROT_READ | UC_PROT_WRITE, or this will return with UC_ERR_MAP error. @return UC_ERR_OK on success, or other value on failure (refer to uc_err enum @@ -428,6 +427,25 @@ uc_err uc_mem_map(uch handle, uint64_t address, size_t size); UNICORN_EXPORT uc_err uc_mem_map_ex(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_MAP error. + @size: size of the memory region to be modified. + This size must be multiple of 4KB, or this will return with UC_ERR_MAP error. + @perms: New permissions for the mapped region. + This must be some combination of UC_PROT_READ | UC_PROT_WRITE, + or this will return with UC_ERR_MAP 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_protect(uch handle, uint64_t start, size_t block_size, uint32_t perms); + #ifdef __cplusplus } #endif diff --git a/qemu/include/exec/memory.h b/qemu/include/exec/memory.h index 7604e8ae..4df8bd85 100755 --- a/qemu/include/exec/memory.h +++ b/qemu/include/exec/memory.h @@ -171,6 +171,7 @@ struct MemoryRegion { NotifierList iommu_notify; struct uc_struct *uc; uint32_t perms; //all perms, partially redundant with readonly + uint64_t end; }; /** diff --git a/qemu/memory.c b/qemu/memory.c index a1d668da..3f8169d9 100755 --- a/qemu/memory.c +++ b/qemu/memory.c @@ -50,9 +50,9 @@ int memory_free(struct uc_struct *uc) int i; get_system_memory(uc)->enabled = false; for (i = 0; i < uc->mapped_block_count; i++) { - uc->mapped_blocks[i].region->enabled = false; - memory_region_del_subregion(get_system_memory(uc), uc->mapped_blocks[i].region); - g_free(uc->mapped_blocks[i].region); + uc->mapped_blocks[i]->enabled = false; + memory_region_del_subregion(get_system_memory(uc), uc->mapped_blocks[i]); + g_free(uc->mapped_blocks[i]); } return 0; } @@ -1315,6 +1315,12 @@ void memory_region_set_readonly(MemoryRegion *mr, bool readonly) if (mr->readonly != readonly) { memory_region_transaction_begin(mr->uc); mr->readonly = readonly; + if (readonly) { + mr->perms &= ~UC_PROT_WRITE; + } + else { + mr->perms |= UC_PROT_WRITE; + } mr->uc->memory_region_update_pending |= mr->enabled; memory_region_transaction_commit(mr->uc); } @@ -1534,6 +1540,7 @@ static void memory_region_add_subregion_common(MemoryRegion *mr, assert(!subregion->container); subregion->container = mr; subregion->addr = offset; + subregion->end = offset + int128_get64(subregion->size); memory_region_update_container_subregions(subregion); } diff --git a/regress/Makefile b/regress/Makefile index ed82f3aa..970f2957 100755 --- a/regress/Makefile +++ b/regress/Makefile @@ -4,7 +4,7 @@ LDFLAGS = -L.. -lunicorn TESTS = map_crash map_write TESTS += sigill sigill2 TESTS += block_test -TESTS += ro_mem_test +TESTS += ro_mem_test mem_protect all: $(TESTS) diff --git a/regress/mem_protect.c b/regress/mem_protect.c new file mode 100755 index 00000000..183a388c --- /dev/null +++ b/regress/mem_protect.c @@ -0,0 +1,237 @@ +#define __STDC_FORMAT_MACROS +#include +#include +#include + +#include + +unsigned char PROGRAM[] = + "\xc7\x05\x00\x00\x40\x00\x41\x41\x41\x41\x90\xc7\x05\x00\x00\x40" + "\x00\x42\x42\x42\x42\x90\xc7\x05\x01\x00\x40\x00\x43\x43\x43\x43" + "\x90\xc7\x05\x01\x00\x40\x00\x44\x44\x44\x44\x90\x66\xc7\x05\x02" + "\x00\x40\x00\x45\x45\x90\x66\xc7\x05\x02\x00\x40\x00\x46\x46\x90" + "\x66\xc7\x05\x01\x00\x40\x00\x47\x47\x90\x66\xc7\x05\x01\x00\x40" + "\x00\x48\x48\x90\xc6\x05\x03\x00\x40\x00\x49\x90\xc6\x05\x03\x00" + "\x40\x00\x4a\x90\xf4"; +// total size: 101 bytes + +/* +bits 32 + +; assumes code section at 0x100000 +; assumes data section at 0x400000, initially rw? + +; with installed hooks toggles UC_PROT_WRITE on each nop + + mov dword [0x400000], 0x41414141 ; aligned + nop + mov dword [0x400000], 0x42424242 ; aligned + nop + mov dword [0x400001], 0x43434343 ; unaligned + nop + mov dword [0x400001], 0x44444444 ; unaligned + nop + mov word [0x400002], 0x4545 ; aligned + nop + mov word [0x400002], 0x4646 ; aligned + nop + mov word [0x400001], 0x4747 ; unaligned + nop + mov word [0x400001], 0x4848 ; unaligned + nop + mov byte [0x400003], 0x49 ; unaligned + nop + mov byte [0x400003], 0x4A ; unaligned + nop + hlt ; tell hook function we are done +*/ + +int test_num = 0; +const uint8_t *tests[] = { + (uint8_t*)"\x41\x41\x41\x41\x00", + (uint8_t*)"\x42\x42\x42\x42\x00", + (uint8_t*)"\x42\x43\x43\x43\x43", + (uint8_t*)"\x42\x44\x44\x44\x44", + (uint8_t*)"\x42\x44\x45\x45\x44", + (uint8_t*)"\x42\x44\x46\x46\x44", + (uint8_t*)"\x42\x47\x47\x46\x44", + (uint8_t*)"\x42\x48\x48\x46\x44", + (uint8_t*)"\x42\x48\x48\x49\x44", + (uint8_t*)"\x42\x48\x48\x4A\x44", +}; + +static int log_num = 1; + +#define CODE_SECTION 0x100000 +#define CODE_SIZE 0x1000 + +#define DATA_SECTION 0x400000 +#define DATA_SIZE 0x1000 + +static uint32_t current_perms = UC_PROT_READ | UC_PROT_WRITE; + +static void hexdump(const char *prefix, const uint8_t *bytes, uint32_t len) { + uint32_t i; + printf("%s", prefix); + for (i = 0; i < len; i++) { + printf("%02hhx", bytes[i]); + } + printf("\n"); +} + +// callback for tracing instruction +static void hook_code(uch handle, uint64_t addr, uint32_t size, void *user_data) { + uint8_t opcode; + uint8_t bytes[5]; + 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%" PRIu64 "\n", log_num++, addr); + _exit(1); + } + printf("ok %d - uc_mem_read for opcode\n", log_num++); + switch (opcode) { + case 0x90: //nop + if (uc_mem_read(handle, DATA_SECTION, bytes, sizeof(bytes)) != UC_ERR_OK) { + printf("not ok %d - uc_mem_read fail for address: 0x%" PRIu64 "\n", log_num++, addr); + _exit(1); + } + printf("ok %d - uc_mem_read for test %d\n", log_num++, test_num); + + if (memcmp(bytes, tests[test_num], sizeof(bytes)) == 0) { + printf("ok %d - passed test %d\n", log_num++, test_num); + } + else { + printf("not ok %d - failed test %d\n", log_num++, test_num); + hexdump("# Expected: ", tests[test_num], sizeof(bytes)); + hexdump("# Received: ", bytes, sizeof(bytes)); + } + test_num++; + current_perms ^= UC_PROT_WRITE; + if (uc_mem_protect(handle, DATA_SECTION, DATA_SIZE, current_perms) != UC_ERR_OK) { + printf("not ok %d - uc_mem_protect fail during hook_code callback, addr: 0x%" PRIu64 "\n", log_num++, addr); + _exit(1); + } + else { + printf("ok %d - uc_mem_protect UC_PROT_WRITE toggled\n", log_num++); + } + break; + case 0xf4: //hlt + if (uc_emu_stop(handle) != UC_ERR_OK) { + printf("not ok %d - uc_emu_stop fail during hook_code callback, addr: 0x%" PRIu64 "\n", log_num++, addr); + _exit(1); + } + else { + printf("ok %d - hlt encountered, uc_emu_stop called\n", log_num++); + } + break; + default: //all others + break; + } +} + +// callback for tracing 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) { + uint8_t bytes[5]; + switch(type) { + default: + printf("not ok %d - UC_HOOK_MEM_INVALID type: %d at 0x%" PRIu64 "\n", log_num++, type, addr); + return false; + case UC_MEM_WRITE_RO: + printf("# RO memory is being WRITTEN at 0x%"PRIx64 ", data size = %u, data value = 0x%"PRIx64 "\n", addr, size, value); + + if (uc_mem_read(handle, DATA_SECTION, bytes, sizeof(bytes)) != UC_ERR_OK) { + printf("not ok %d - uc_mem_read fail for address: 0x%" PRIu64 "\n", log_num++, addr); + _exit(1); + } + printf("ok %d - uc_mem_read for ro side of test %d\n", log_num++, test_num - 1); + + if (memcmp(bytes, tests[test_num - 1], sizeof(bytes)) == 0) { + printf("ok %d - passed ro side of test %d\n", log_num++, test_num - 1); + } + else { + printf("ok %d - failed ro side of test %d\n", log_num++, test_num - 1); + hexdump("# Expected: ", tests[test_num - 1], sizeof(bytes)); + hexdump("# Received: ", bytes, sizeof(bytes)); + } + + current_perms |= UC_PROT_WRITE; + if (uc_mem_protect(handle, DATA_SECTION, DATA_SIZE, current_perms) != UC_ERR_OK) { + printf("not ok %d - uc_mem_protect fail during hook_mem_invalid callback, addr: 0x%" PRIu64 "\n", log_num++, addr); + _exit(1); + } + else { + printf("ok %d - uc_mem_protect UC_PROT_WRITE toggled\n", log_num++); + } + return true; + } +} + +int main(int argc, char **argv, char **envp) { + uch handle, trace1, trace2; + uc_err err; + uint8_t bytes[8]; + uint32_t esp; + int result; + + 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_ex(handle, CODE_SECTION, CODE_SIZE, UC_PROT_READ); + uc_mem_map_ex(handle, DATA_SECTION, DATA_SIZE, current_perms); + + // 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 2; + } + 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 3; + } + else { + printf("ok %d - UC_HOOK_CODE 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 4; + } + 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, 100); + 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 5; + } + else { + printf("ok %d - uc_emu_start complete\n", log_num++); + } + printf("# END execution\n"); + + 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/regress/ro_mem_test.c b/regress/ro_mem_test.c index bacd5b3a..56a04586 100755 --- a/regress/ro_mem_test.c +++ b/regress/ro_mem_test.c @@ -96,7 +96,7 @@ int main(int argc, char **argv, char **envp) { uc_mem_map(handle, 0x100000, 0x1000); uc_mem_map(handle, 0x200000, 0x2000); uc_mem_map(handle, 0x300000, 0x3000); - uc_mem_map_ex(handle, 0x400000, 0x4000, UC_PROT_READ | UC_PROT_EXEC); + uc_mem_map_ex(handle, 0x400000, 0x4000, UC_PROT_READ); if (map_stack) { printf("Pre-mapping stack\n"); diff --git a/uc.c b/uc.c index 2db8461c..fdd968a9 100755 --- a/uc.c +++ b/uc.c @@ -352,13 +352,14 @@ uc_err uc_mem_read(uch handle, uint64_t address, uint8_t *bytes, size_t size) return UC_ERR_OK; } -static const MemoryBlock *getMemoryBlock(struct uc_struct *uc, uint64_t address); -static const MemoryBlock *getMemoryBlock(struct uc_struct *uc, uint64_t address) { + +static MemoryRegion *getMemoryBlock(struct uc_struct *uc, uint64_t address); +static MemoryRegion *getMemoryBlock(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 &uc->mapped_blocks[i]; + if (address >= uc->mapped_blocks[i]->addr && address < uc->mapped_blocks[i]->end) + return uc->mapped_blocks[i]; } // not found @@ -369,25 +370,27 @@ UNICORN_EXPORT uc_err uc_mem_write(uch handle, uint64_t address, const uint8_t *bytes, size_t size) { struct uc_struct *uc = (struct uc_struct *)(uintptr_t)handle; + uint32_t operms; if (handle == 0) // invalid handle return UC_ERR_UCH; - const MemoryBlock *mb = getMemoryBlock(uc, address); - if (mb == NULL) + MemoryRegion *mr = getMemoryBlock(uc, address); + if (mr == NULL) return UC_ERR_MEM_WRITE; - if (!(mb->region->perms & UC_PROT_WRITE)) //write protected + operms = mr->perms; + if (!(operms & UC_PROT_WRITE)) //write protected //but this is not the program accessing memory, so temporarily mark writable - uc->readonly_mem(mb->region, false); + uc->readonly_mem(mr, false); if (uc->write_mem(&uc->as, address, bytes, size) == false) return UC_ERR_MEM_WRITE; - if (!(mb->region->perms & UC_PROT_WRITE)) //write protected + if (!(operms & UC_PROT_WRITE)) //write protected //now write protect it again - uc->readonly_mem(mb->region, true); + uc->readonly_mem(mr, true); return UC_ERR_OK; } @@ -556,7 +559,7 @@ static uc_err _hook_mem_access(uch handle, uc_mem_type type, UNICORN_EXPORT uc_err uc_mem_map_ex(uch handle, uint64_t address, size_t size, uint32_t perms) { - MemoryBlock *blocks; + MemoryRegion **regions; struct uc_struct* uc = (struct uc_struct *)handle; if (handle == 0) @@ -576,19 +579,17 @@ uc_err uc_mem_map_ex(uch handle, uint64_t address, size_t size, uint32_t perms) return UC_ERR_MAP; // check for only valid permissions - if ((perms & ~(UC_PROT_READ | UC_PROT_WRITE | UC_PROT_EXEC)) != 0) + if ((perms & ~(UC_PROT_READ | UC_PROT_WRITE)) != 0) return UC_ERR_MAP; if ((uc->mapped_block_count & (MEM_BLOCK_INCR - 1)) == 0) { //time to grow - blocks = realloc(uc->mapped_blocks, sizeof(MemoryBlock) * (uc->mapped_block_count + MEM_BLOCK_INCR)); - if (blocks == NULL) { + regions = (MemoryRegion**)realloc(uc->mapped_blocks, sizeof(MemoryRegion*) * (uc->mapped_block_count + MEM_BLOCK_INCR)); + if (regions == NULL) { return UC_ERR_OOM; } - uc->mapped_blocks = blocks; + uc->mapped_blocks = regions; } - 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].region = uc->memory_map(uc, address, size, perms); + uc->mapped_blocks[uc->mapped_block_count] = uc->memory_map(uc, address, size, perms); uc->mapped_block_count++; return UC_ERR_OK; @@ -597,8 +598,92 @@ uc_err uc_mem_map_ex(uch handle, uint64_t address, size_t size, uint32_t perms) UNICORN_EXPORT uc_err uc_mem_map(uch handle, uint64_t address, size_t size) { - //old api, maps RWX by default - return uc_mem_map_ex(handle, address, size, UC_PROT_READ | UC_PROT_WRITE | UC_PROT_EXEC); + //old api, maps RW by default + return uc_mem_map_ex(handle, address, size, UC_PROT_READ | UC_PROT_WRITE); +} + +UNICORN_EXPORT +uc_err uc_mem_protect(uch handle, uint64_t start, size_t block_size, uint32_t perms) +{ + uint64_t address; + uint64_t size; + struct uc_struct* uc = (struct uc_struct *)handle; + + if (handle == 0) + // invalid handle + return UC_ERR_UCH; + + if (block_size == 0) + // invalid memory mapping + return UC_ERR_MAP; + + // address must be aligned to 4KB + if ((start & (4*1024 - 1)) != 0) + return UC_ERR_MAP; + + // size must be multiple of 4KB + if ((block_size & (4*1024 - 1)) != 0) + return UC_ERR_MAP; + + // check for only valid permissions + if ((perms & ~(UC_PROT_READ | UC_PROT_WRITE)) != 0) + return UC_ERR_MAP; + + //check that users entire requested block is mapped + address = start; + size = block_size; + while (size > 0) { + uint64_t region_size; + MemoryRegion *mr = memory_mapping(uc, address); + if (mr == NULL) { + return UC_ERR_MAP; + } + region_size = int128_get64(mr->size); + if (address > mr->addr) { + //in case start address is not aligned with start of region + region_size -= address - mr->addr; + } + if (size < region_size) { + //entire region is covered + break; + } + size -= region_size; + address += region_size; + } + + //Now we know entire region is mapped, so change permissions + address = start; + size = block_size; + while (size > 0) { + MemoryRegion *mr = memory_mapping(uc, address); + uint64_t region_size = int128_get64(mr->size); + if (address > mr->addr) { + //in case start address is not aligned with start of region + region_size -= address - mr->addr; + //TODO Learn how to split regions + //In this case some proper subset of the region is having it's permissions changed + //need to split region and add new portions into uc->mapped_blocks list + //In this case, there is a portion of the region with original perms: mr->addr..start + //and a portion getting new perms: start..start+block_size + + //split the block and stay in the loop + } + if (size < int128_get64(mr->size)) { + //TODO Learn how to split regions + //In this case some proper subset of the region is having it's permissions changed + //need to split region and add new portions into uc->mapped_blocks list + //In this case, there is a portion of the region with new perms: start..start+block_size + //and a portion getting new perms: mr->addr+size..mr->addr+mr->size + + //split the block and break + break; + } + size -= int128_get64(mr->size); + address += int128_get64(mr->size); + mr->perms = perms; + uc->readonly_mem(mr, (perms & UC_PROT_WRITE) == 0); + } + return UC_ERR_OK; } MemoryRegion *memory_mapping(struct uc_struct* uc, uint64_t address) @@ -606,8 +691,8 @@ 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 uc->mapped_blocks[i].region; + if (address >= uc->mapped_blocks[i]->addr && address < uc->mapped_blocks[i]->end) + return uc->mapped_blocks[i]; } // not found From b31bb9638c730b340c535ef3cd07e83f5eb2a3f5 Mon Sep 17 00:00:00 2001 From: Chris Eagle Date: Fri, 28 Aug 2015 00:00:09 -0700 Subject: [PATCH 10/14] cleanup for pull request --- {regress => samples}/mem_protect.c | 12 ++++++++---- uc.c | 6 ++++-- 2 files changed, 12 insertions(+), 6 deletions(-) rename {regress => samples}/mem_protect.c (98%) diff --git a/regress/mem_protect.c b/samples/mem_protect.c similarity index 98% rename from regress/mem_protect.c rename to samples/mem_protect.c index 183a388c..e4c09201 100755 --- a/regress/mem_protect.c +++ b/samples/mem_protect.c @@ -70,7 +70,8 @@ static int log_num = 1; static uint32_t current_perms = UC_PROT_READ | UC_PROT_WRITE; -static void hexdump(const char *prefix, const uint8_t *bytes, uint32_t len) { +static void hexdump(const char *prefix, const uint8_t *bytes, uint32_t len) +{ uint32_t i; printf("%s", prefix); for (i = 0; i < len; i++) { @@ -80,7 +81,8 @@ static void hexdump(const char *prefix, const uint8_t *bytes, uint32_t len) { } // callback for tracing instruction -static void hook_code(uch handle, uint64_t addr, uint32_t size, void *user_data) { +static void hook_code(uch handle, uint64_t addr, uint32_t size, void *user_data) +{ uint8_t opcode; uint8_t bytes[5]; if (uc_mem_read(handle, addr, &opcode, 1) != UC_ERR_OK) { @@ -130,7 +132,8 @@ static void hook_code(uch handle, uint64_t addr, uint32_t size, void *user_data) // callback for tracing 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) { + uint64_t addr, int size, int64_t value, void *user_data) +{ uint8_t bytes[5]; switch(type) { default: @@ -166,7 +169,8 @@ static bool hook_mem_invalid(uch handle, uc_mem_type type, } } -int main(int argc, char **argv, char **envp) { +int main(int argc, char **argv, char **envp) +{ uch handle, trace1, trace2; uc_err err; uint8_t bytes[8]; diff --git a/uc.c b/uc.c index fdd968a9..d219b7ba 100755 --- a/uc.c +++ b/uc.c @@ -31,6 +31,8 @@ #include "qemu/include/hw/boards.h" +static MemoryRegion *getMemoryBlock(struct uc_struct *uc, uint64_t address); + UNICORN_EXPORT unsigned int uc_version(unsigned int *major, unsigned int *minor) { @@ -353,8 +355,8 @@ uc_err uc_mem_read(uch handle, uint64_t address, uint8_t *bytes, size_t size) return UC_ERR_OK; } -static MemoryRegion *getMemoryBlock(struct uc_struct *uc, uint64_t address); -static MemoryRegion *getMemoryBlock(struct uc_struct *uc, uint64_t address) { +static MemoryRegion *getMemoryBlock(struct uc_struct *uc, uint64_t address) +{ unsigned int i; for(i = 0; i < uc->mapped_block_count; i++) { From 4f11d88d28ece7e3a4604564fa65ded5a18af923 Mon Sep 17 00:00:00 2001 From: Chris Eagle Date: Fri, 28 Aug 2015 00:17:33 -0700 Subject: [PATCH 11/14] Makefile cleanup --- regress/Makefile | 2 +- samples/Makefile | 3 ++- samples/mem_protect.c | 3 --- 3 files changed, 3 insertions(+), 5 deletions(-) mode change 100644 => 100755 samples/Makefile diff --git a/regress/Makefile b/regress/Makefile index 970f2957..ed82f3aa 100755 --- a/regress/Makefile +++ b/regress/Makefile @@ -4,7 +4,7 @@ LDFLAGS = -L.. -lunicorn TESTS = map_crash map_write TESTS += sigill sigill2 TESTS += block_test -TESTS += ro_mem_test mem_protect +TESTS += ro_mem_test all: $(TESTS) diff --git a/samples/Makefile b/samples/Makefile old mode 100644 new mode 100755 index 1ebb891f..2c814006 --- a/samples/Makefile +++ b/samples/Makefile @@ -97,6 +97,7 @@ endif ifneq (,$(findstring x86,$(UNICORN_ARCHS))) SOURCES += sample_x86.c SOURCES += shellcode.c +SOURCES += mem_protect.c endif ifneq (,$(findstring m68k,$(UNICORN_ARCHS))) SOURCES += sample_m68k.c @@ -111,7 +112,7 @@ 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_protect $(BINARY): $(OBJS) diff --git a/samples/mem_protect.c b/samples/mem_protect.c index e4c09201..4f0a9040 100755 --- a/samples/mem_protect.c +++ b/samples/mem_protect.c @@ -173,9 +173,6 @@ int main(int argc, char **argv, char **envp) { uch handle, trace1, trace2; uc_err err; - uint8_t bytes[8]; - uint32_t esp; - int result; printf("# Memory protect test\n"); From 71ddad94740b9dd23228325e62c93597663af316 Mon Sep 17 00:00:00 2001 From: Chris Eagle Date: Fri, 28 Aug 2015 00:30:50 -0700 Subject: [PATCH 12/14] Doc cleanup --- include/unicorn/unicorn.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/unicorn/unicorn.h b/include/unicorn/unicorn.h index dd4acbee..376c0eec 100755 --- a/include/unicorn/unicorn.h +++ b/include/unicorn/unicorn.h @@ -432,9 +432,9 @@ uc_err uc_mem_map_ex(uch handle, uint64_t address, size_t size, uint32_t perms); 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. + @start: starting address of the memory region to be modified. This address must be aligned to 4KB, or this will return with UC_ERR_MAP error. - @size: size of the memory region to be modified. + @block_size: size of the memory region to be modified. This size must be multiple of 4KB, or this will return with UC_ERR_MAP error. @perms: New permissions for the mapped region. This must be some combination of UC_PROT_READ | UC_PROT_WRITE, From adc254cc745b1977513779453033d806f822b376 Mon Sep 17 00:00:00 2001 From: Chris Eagle Date: Fri, 28 Aug 2015 01:37:49 -0700 Subject: [PATCH 13/14] Roll back uc_mem_protect changes --- include/unicorn/unicorn.h | 19 --- samples/Makefile | 3 +- samples/mem_protect.c | 238 -------------------------------------- uc.c | 84 -------------- 4 files changed, 1 insertion(+), 343 deletions(-) delete mode 100755 samples/mem_protect.c diff --git a/include/unicorn/unicorn.h b/include/unicorn/unicorn.h index 376c0eec..ddb96d80 100755 --- a/include/unicorn/unicorn.h +++ b/include/unicorn/unicorn.h @@ -427,25 +427,6 @@ uc_err uc_mem_map(uch handle, uint64_t address, size_t size); UNICORN_EXPORT uc_err uc_mem_map_ex(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() - @start: starting address of the memory region to be modified. - This address must be aligned to 4KB, or this will return with UC_ERR_MAP error. - @block_size: size of the memory region to be modified. - This size must be multiple of 4KB, or this will return with UC_ERR_MAP error. - @perms: New permissions for the mapped region. - This must be some combination of UC_PROT_READ | UC_PROT_WRITE, - or this will return with UC_ERR_MAP 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_protect(uch handle, uint64_t start, size_t block_size, uint32_t perms); - #ifdef __cplusplus } #endif diff --git a/samples/Makefile b/samples/Makefile index 2c814006..1ebb891f 100755 --- a/samples/Makefile +++ b/samples/Makefile @@ -97,7 +97,6 @@ endif ifneq (,$(findstring x86,$(UNICORN_ARCHS))) SOURCES += sample_x86.c SOURCES += shellcode.c -SOURCES += mem_protect.c endif ifneq (,$(findstring m68k,$(UNICORN_ARCHS))) SOURCES += sample_m68k.c @@ -112,7 +111,7 @@ 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 mem_protect + rm -rf sample_x86 sample_arm sample_arm64 sample_mips sample_sparc sample_ppc sample_m68k shellcode $(BINARY): $(OBJS) diff --git a/samples/mem_protect.c b/samples/mem_protect.c deleted file mode 100755 index 4f0a9040..00000000 --- a/samples/mem_protect.c +++ /dev/null @@ -1,238 +0,0 @@ -#define __STDC_FORMAT_MACROS -#include -#include -#include - -#include - -unsigned char PROGRAM[] = - "\xc7\x05\x00\x00\x40\x00\x41\x41\x41\x41\x90\xc7\x05\x00\x00\x40" - "\x00\x42\x42\x42\x42\x90\xc7\x05\x01\x00\x40\x00\x43\x43\x43\x43" - "\x90\xc7\x05\x01\x00\x40\x00\x44\x44\x44\x44\x90\x66\xc7\x05\x02" - "\x00\x40\x00\x45\x45\x90\x66\xc7\x05\x02\x00\x40\x00\x46\x46\x90" - "\x66\xc7\x05\x01\x00\x40\x00\x47\x47\x90\x66\xc7\x05\x01\x00\x40" - "\x00\x48\x48\x90\xc6\x05\x03\x00\x40\x00\x49\x90\xc6\x05\x03\x00" - "\x40\x00\x4a\x90\xf4"; -// total size: 101 bytes - -/* -bits 32 - -; assumes code section at 0x100000 -; assumes data section at 0x400000, initially rw? - -; with installed hooks toggles UC_PROT_WRITE on each nop - - mov dword [0x400000], 0x41414141 ; aligned - nop - mov dword [0x400000], 0x42424242 ; aligned - nop - mov dword [0x400001], 0x43434343 ; unaligned - nop - mov dword [0x400001], 0x44444444 ; unaligned - nop - mov word [0x400002], 0x4545 ; aligned - nop - mov word [0x400002], 0x4646 ; aligned - nop - mov word [0x400001], 0x4747 ; unaligned - nop - mov word [0x400001], 0x4848 ; unaligned - nop - mov byte [0x400003], 0x49 ; unaligned - nop - mov byte [0x400003], 0x4A ; unaligned - nop - hlt ; tell hook function we are done -*/ - -int test_num = 0; -const uint8_t *tests[] = { - (uint8_t*)"\x41\x41\x41\x41\x00", - (uint8_t*)"\x42\x42\x42\x42\x00", - (uint8_t*)"\x42\x43\x43\x43\x43", - (uint8_t*)"\x42\x44\x44\x44\x44", - (uint8_t*)"\x42\x44\x45\x45\x44", - (uint8_t*)"\x42\x44\x46\x46\x44", - (uint8_t*)"\x42\x47\x47\x46\x44", - (uint8_t*)"\x42\x48\x48\x46\x44", - (uint8_t*)"\x42\x48\x48\x49\x44", - (uint8_t*)"\x42\x48\x48\x4A\x44", -}; - -static int log_num = 1; - -#define CODE_SECTION 0x100000 -#define CODE_SIZE 0x1000 - -#define DATA_SECTION 0x400000 -#define DATA_SIZE 0x1000 - -static uint32_t current_perms = UC_PROT_READ | UC_PROT_WRITE; - -static void hexdump(const char *prefix, const uint8_t *bytes, uint32_t len) -{ - uint32_t i; - printf("%s", prefix); - for (i = 0; i < len; i++) { - printf("%02hhx", bytes[i]); - } - printf("\n"); -} - -// callback for tracing instruction -static void hook_code(uch handle, uint64_t addr, uint32_t size, void *user_data) -{ - uint8_t opcode; - uint8_t bytes[5]; - 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%" PRIu64 "\n", log_num++, addr); - _exit(1); - } - printf("ok %d - uc_mem_read for opcode\n", log_num++); - switch (opcode) { - case 0x90: //nop - if (uc_mem_read(handle, DATA_SECTION, bytes, sizeof(bytes)) != UC_ERR_OK) { - printf("not ok %d - uc_mem_read fail for address: 0x%" PRIu64 "\n", log_num++, addr); - _exit(1); - } - printf("ok %d - uc_mem_read for test %d\n", log_num++, test_num); - - if (memcmp(bytes, tests[test_num], sizeof(bytes)) == 0) { - printf("ok %d - passed test %d\n", log_num++, test_num); - } - else { - printf("not ok %d - failed test %d\n", log_num++, test_num); - hexdump("# Expected: ", tests[test_num], sizeof(bytes)); - hexdump("# Received: ", bytes, sizeof(bytes)); - } - test_num++; - current_perms ^= UC_PROT_WRITE; - if (uc_mem_protect(handle, DATA_SECTION, DATA_SIZE, current_perms) != UC_ERR_OK) { - printf("not ok %d - uc_mem_protect fail during hook_code callback, addr: 0x%" PRIu64 "\n", log_num++, addr); - _exit(1); - } - else { - printf("ok %d - uc_mem_protect UC_PROT_WRITE toggled\n", log_num++); - } - break; - case 0xf4: //hlt - if (uc_emu_stop(handle) != UC_ERR_OK) { - printf("not ok %d - uc_emu_stop fail during hook_code callback, addr: 0x%" PRIu64 "\n", log_num++, addr); - _exit(1); - } - else { - printf("ok %d - hlt encountered, uc_emu_stop called\n", log_num++); - } - break; - default: //all others - break; - } -} - -// callback for tracing 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) -{ - uint8_t bytes[5]; - switch(type) { - default: - printf("not ok %d - UC_HOOK_MEM_INVALID type: %d at 0x%" PRIu64 "\n", log_num++, type, addr); - return false; - case UC_MEM_WRITE_RO: - printf("# RO memory is being WRITTEN at 0x%"PRIx64 ", data size = %u, data value = 0x%"PRIx64 "\n", addr, size, value); - - if (uc_mem_read(handle, DATA_SECTION, bytes, sizeof(bytes)) != UC_ERR_OK) { - printf("not ok %d - uc_mem_read fail for address: 0x%" PRIu64 "\n", log_num++, addr); - _exit(1); - } - printf("ok %d - uc_mem_read for ro side of test %d\n", log_num++, test_num - 1); - - if (memcmp(bytes, tests[test_num - 1], sizeof(bytes)) == 0) { - printf("ok %d - passed ro side of test %d\n", log_num++, test_num - 1); - } - else { - printf("ok %d - failed ro side of test %d\n", log_num++, test_num - 1); - hexdump("# Expected: ", tests[test_num - 1], sizeof(bytes)); - hexdump("# Received: ", bytes, sizeof(bytes)); - } - - current_perms |= UC_PROT_WRITE; - if (uc_mem_protect(handle, DATA_SECTION, DATA_SIZE, current_perms) != UC_ERR_OK) { - printf("not ok %d - uc_mem_protect fail during hook_mem_invalid callback, addr: 0x%" PRIu64 "\n", log_num++, addr); - _exit(1); - } - else { - printf("ok %d - uc_mem_protect UC_PROT_WRITE toggled\n", log_num++); - } - return true; - } -} - -int main(int argc, char **argv, char **envp) -{ - uch handle, trace1, trace2; - uc_err err; - - 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_ex(handle, CODE_SECTION, CODE_SIZE, UC_PROT_READ); - uc_mem_map_ex(handle, DATA_SECTION, DATA_SIZE, current_perms); - - // 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 2; - } - 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 3; - } - else { - printf("ok %d - UC_HOOK_CODE 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 4; - } - 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, 100); - 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 5; - } - else { - printf("ok %d - uc_emu_start complete\n", log_num++); - } - printf("# END execution\n"); - - 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 d219b7ba..5bbaca75 100755 --- a/uc.c +++ b/uc.c @@ -604,90 +604,6 @@ 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); } -UNICORN_EXPORT -uc_err uc_mem_protect(uch handle, uint64_t start, size_t block_size, uint32_t perms) -{ - uint64_t address; - uint64_t size; - struct uc_struct* uc = (struct uc_struct *)handle; - - if (handle == 0) - // invalid handle - return UC_ERR_UCH; - - if (block_size == 0) - // invalid memory mapping - return UC_ERR_MAP; - - // address must be aligned to 4KB - if ((start & (4*1024 - 1)) != 0) - return UC_ERR_MAP; - - // size must be multiple of 4KB - if ((block_size & (4*1024 - 1)) != 0) - return UC_ERR_MAP; - - // check for only valid permissions - if ((perms & ~(UC_PROT_READ | UC_PROT_WRITE)) != 0) - return UC_ERR_MAP; - - //check that users entire requested block is mapped - address = start; - size = block_size; - while (size > 0) { - uint64_t region_size; - MemoryRegion *mr = memory_mapping(uc, address); - if (mr == NULL) { - return UC_ERR_MAP; - } - region_size = int128_get64(mr->size); - if (address > mr->addr) { - //in case start address is not aligned with start of region - region_size -= address - mr->addr; - } - if (size < region_size) { - //entire region is covered - break; - } - size -= region_size; - address += region_size; - } - - //Now we know entire region is mapped, so change permissions - address = start; - size = block_size; - while (size > 0) { - MemoryRegion *mr = memory_mapping(uc, address); - uint64_t region_size = int128_get64(mr->size); - if (address > mr->addr) { - //in case start address is not aligned with start of region - region_size -= address - mr->addr; - //TODO Learn how to split regions - //In this case some proper subset of the region is having it's permissions changed - //need to split region and add new portions into uc->mapped_blocks list - //In this case, there is a portion of the region with original perms: mr->addr..start - //and a portion getting new perms: start..start+block_size - - //split the block and stay in the loop - } - if (size < int128_get64(mr->size)) { - //TODO Learn how to split regions - //In this case some proper subset of the region is having it's permissions changed - //need to split region and add new portions into uc->mapped_blocks list - //In this case, there is a portion of the region with new perms: start..start+block_size - //and a portion getting new perms: mr->addr+size..mr->addr+mr->size - - //split the block and break - break; - } - size -= int128_get64(mr->size); - address += int128_get64(mr->size); - mr->perms = perms; - uc->readonly_mem(mr, (perms & UC_PROT_WRITE) == 0); - } - return UC_ERR_OK; -} - MemoryRegion *memory_mapping(struct uc_struct* uc, uint64_t address) { unsigned int i; From aa509cc00f6174d875dec937fb826a85e2428f3c Mon Sep 17 00:00:00 2001 From: Chris Eagle Date: Fri, 28 Aug 2015 02:20:56 -0700 Subject: [PATCH 14/14] delete redundant function --- uc.c | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/uc.c b/uc.c index 5bbaca75..0352380c 100755 --- a/uc.c +++ b/uc.c @@ -31,8 +31,6 @@ #include "qemu/include/hw/boards.h" -static MemoryRegion *getMemoryBlock(struct uc_struct *uc, uint64_t address); - UNICORN_EXPORT unsigned int uc_version(unsigned int *major, unsigned int *minor) { @@ -355,19 +353,6 @@ uc_err uc_mem_read(uch handle, uint64_t address, uint8_t *bytes, size_t size) return UC_ERR_OK; } -static MemoryRegion *getMemoryBlock(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]->addr && address < uc->mapped_blocks[i]->end) - return uc->mapped_blocks[i]; - } - - // not found - return NULL; -} - UNICORN_EXPORT uc_err uc_mem_write(uch handle, uint64_t address, const uint8_t *bytes, size_t size) { @@ -378,7 +363,7 @@ uc_err uc_mem_write(uch handle, uint64_t address, const uint8_t *bytes, size_t s // invalid handle return UC_ERR_UCH; - MemoryRegion *mr = getMemoryBlock(uc, address); + MemoryRegion *mr = memory_mapping(uc, address); if (mr == NULL) return UC_ERR_MEM_WRITE;