#include "unicorn_test.h" const uint64_t code_start = 0x1000; const uint64_t code_len = 0x4000; static void uc_common_setup(uc_engine **uc, uc_arch arch, uc_mode mode, const char *code, uint64_t size) { OK(uc_open(arch, mode, uc)); OK(uc_mem_map(*uc, code_start, code_len, UC_PROT_ALL)); OK(uc_mem_write(*uc, code_start, code, size)); } typedef struct _INSN_IN_RESULT { uint32_t port; int size; } INSN_IN_RESULT; static void test_x86_in_callback(uc_engine *uc, uint32_t port, int size, void *user_data) { INSN_IN_RESULT *result = (INSN_IN_RESULT *)user_data; result->port = port; result->size = size; } static void test_x86_in() { uc_engine *uc; uc_hook hook; char code[] = "\xe5\x10"; // IN eax, 0x10 INSN_IN_RESULT result; uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, code, sizeof(code) - 1); OK(uc_hook_add(uc, &hook, UC_HOOK_INSN, test_x86_in_callback, &result, 1, 0, UC_X86_INS_IN)); OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 0)); TEST_CHECK(result.port == 0x10); TEST_CHECK(result.size == 4); OK(uc_hook_del(uc, hook)); OK(uc_close(uc)); } typedef struct _INSN_OUT_RESULT { uint32_t port; int size; uint32_t value; } INSN_OUT_RESULT; static void test_x86_out_callback(uc_engine *uc, uint32_t port, int size, uint32_t value, void *user_data) { INSN_OUT_RESULT *result = (INSN_OUT_RESULT *)user_data; result->port = port; result->size = size; result->value = value; } static void test_x86_out() { uc_engine *uc; uc_hook hook; char code[] = "\xb0\x32\xe6\x46"; // MOV al, 0x32; OUT 0x46, al; INSN_OUT_RESULT result; uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, code, sizeof(code) - 1); OK(uc_hook_add(uc, &hook, UC_HOOK_INSN, test_x86_out_callback, &result, 1, 0, UC_X86_INS_OUT)); OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 0)); TEST_CHECK(result.port == 0x46); TEST_CHECK(result.size == 1); TEST_CHECK(result.value == 0x32); OK(uc_hook_del(uc, hook)); OK(uc_close(uc)); } typedef struct _MEM_HOOK_RESULT { uc_mem_type type; uint64_t address; int size; uint64_t value; } MEM_HOOK_RESULT; typedef struct _MEM_HOOK_RESULTS { uint64_t count; MEM_HOOK_RESULT results[16]; } MEM_HOOK_RESULTS; static bool test_x86_mem_hook_all_callback(uc_engine *uc, uc_mem_type type, uint64_t address, int size, uint64_t value, void *user_data) { MEM_HOOK_RESULTS *r = (MEM_HOOK_RESULTS *)user_data; uint64_t count = r->count; if (count >= 16) { TEST_ASSERT(false); } r->results[count].type = type; r->results[count].address = address; r->results[count].size = size; r->results[count].value = value; r->count++; if (type == UC_MEM_READ_UNMAPPED) { uc_mem_map(uc, address, 0x1000, UC_PROT_ALL); } return true; } static void test_x86_mem_hook_all() { uc_engine *uc; uc_hook hook; // mov eax, 0xdeadbeef; // mov [0x8000], eax; // mov eax, [0x10000]; char code[] = "\xb8\xef\xbe\xad\xde\xa3\x00\x80\x00\x00\xa1\x00\x00\x01\x00"; MEM_HOOK_RESULTS r = {0}; MEM_HOOK_RESULT expects[3] = {{UC_MEM_WRITE, 0x8000, 4, 0xdeadbeef}, {UC_MEM_READ_UNMAPPED, 0x10000, 4, 0}, {UC_MEM_READ, 0x10000, 4, 0}}; uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, code, sizeof(code) - 1); OK(uc_mem_map(uc, 0x8000, 0x1000, UC_PROT_ALL)); OK(uc_hook_add(uc, &hook, UC_HOOK_MEM_VALID | UC_HOOK_MEM_INVALID, test_x86_mem_hook_all_callback, &r, 1, 0)); OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 0)); TEST_CHECK(r.count == 3); for (int i = 0; i < r.count; i++) { TEST_CHECK(expects[i].type == r.results[i].type); TEST_CHECK(expects[i].address == r.results[i].address); TEST_CHECK(expects[i].size == r.results[i].size); TEST_CHECK(expects[i].value == r.results[i].value); } OK(uc_hook_del(uc, hook)); OK(uc_close(uc)); } static void test_x86_inc_dec_pxor() { uc_engine *uc; char code[] = "\x41\x4a\x66\x0f\xef\xc1"; // INC ecx; DEC edx; PXOR xmm0, xmm1 int r_ecx = 0x1234; int r_edx = 0x7890; uint64_t r_xmm0[2] = {0x08090a0b0c0d0e0f, 0x0001020304050607}; uint64_t r_xmm1[2] = {0x8090a0b0c0d0e0f0, 0x0010203040506070}; uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, code, sizeof(code) - 1); OK(uc_reg_write(uc, UC_X86_REG_ECX, &r_ecx)); OK(uc_reg_write(uc, UC_X86_REG_EDX, &r_edx)); OK(uc_reg_write(uc, UC_X86_REG_XMM0, &r_xmm0)); OK(uc_reg_write(uc, UC_X86_REG_XMM1, &r_xmm1)); OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 0)); OK(uc_reg_read(uc, UC_X86_REG_ECX, &r_ecx)); OK(uc_reg_read(uc, UC_X86_REG_EDX, &r_edx)); OK(uc_reg_read(uc, UC_X86_REG_XMM0, &r_xmm0)); TEST_CHECK(r_ecx == 0x1235); TEST_CHECK(r_edx == 0x788f); TEST_CHECK(r_xmm0[0] == 0x8899aabbccddeeff); TEST_CHECK(r_xmm0[1] == 0x0011223344556677); OK(uc_close(uc)); } static void test_x86_relative_jump() { uc_engine *uc; char code[] = "\xeb\x02\x90\x90\x90\x90\x90\x90"; // jmp 4; nop; nop; nop; // nop; nop; nop int r_eip; uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, code, sizeof(code) - 1); OK(uc_emu_start(uc, code_start, code_start + 4, 0, 0)); OK(uc_reg_read(uc, UC_X86_REG_EIP, &r_eip)); TEST_CHECK(r_eip == code_start + 4); OK(uc_close(uc)); } static void test_x86_loop() { uc_engine *uc; char code[] = "\x41\x4a\xeb\xfe"; // inc ecx; dec edx; jmp $; int r_ecx = 0x1234; int r_edx = 0x7890; uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, code, sizeof(code) - 1); OK(uc_reg_write(uc, UC_X86_REG_ECX, &r_ecx)); OK(uc_reg_write(uc, UC_X86_REG_EDX, &r_edx)); OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 1 * 1000000, 0)); OK(uc_reg_read(uc, UC_X86_REG_ECX, &r_ecx)); OK(uc_reg_read(uc, UC_X86_REG_EDX, &r_edx)); TEST_CHECK(r_ecx == 0x1235); TEST_CHECK(r_edx == 0x788f); OK(uc_close(uc)); } static void test_x86_invalid_mem_read() { uc_engine *uc; char code[] = "\x8b\x0d\xaa\xaa\xaa\xaa"; // mov ecx, [0xAAAAAAAA] uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, code, sizeof(code) - 1); uc_assert_err( UC_ERR_READ_UNMAPPED, uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 0)); OK(uc_close(uc)); } static void test_x86_invalid_mem_write() { uc_engine *uc; char code[] = "\x89\x0d\xaa\xaa\xaa\xaa"; // mov ecx, [0xAAAAAAAA] uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, code, sizeof(code) - 1); uc_assert_err( UC_ERR_WRITE_UNMAPPED, uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 0)); OK(uc_close(uc)); } static void test_x86_invalid_jump() { uc_engine *uc; char code[] = "\xe9\xe9\xee\xee\xee"; // jmp 0xEEEEEEEE uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, code, sizeof(code) - 1); uc_assert_err( UC_ERR_FETCH_UNMAPPED, uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 0)); OK(uc_close(uc)); } static void test_x86_64_syscall_callback(uc_engine *uc, void *user_data) { uint64_t rax; OK(uc_reg_read(uc, UC_X86_REG_RAX, &rax)); TEST_CHECK(rax == 0x100); } static void test_x86_64_syscall() { uc_engine *uc; uc_hook hook; char code[] = "\x0f\x05"; // syscall uint64_t r_rax = 0x100; uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_64, code, sizeof(code) - 1); OK(uc_reg_write(uc, UC_X86_REG_RAX, &r_rax)); OK(uc_hook_add(uc, &hook, UC_HOOK_INSN, test_x86_64_syscall_callback, NULL, 1, 0, UC_X86_INS_SYSCALL)); OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 0)); OK(uc_hook_del(uc, hook)); OK(uc_close(uc)); } static void test_x86_16_add() { uc_engine *uc; char code[] = "\x00\x00"; // add byte ptr [bx + si], al uint16_t r_ax = 7; uint16_t r_bx = 5; uint16_t r_si = 6; uint8_t result; uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_16, code, sizeof(code) - 1); OK(uc_mem_map(uc, 0, 0x1000, UC_PROT_ALL)); OK(uc_reg_write(uc, UC_X86_REG_AX, &r_ax)); OK(uc_reg_write(uc, UC_X86_REG_BX, &r_bx)); OK(uc_reg_write(uc, UC_X86_REG_SI, &r_si)); OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 0)); OK(uc_mem_read(uc, r_bx + r_si, &result, 1)); TEST_CHECK(result == 7); OK(uc_close(uc)); } static void test_x86_reg_save() { uc_engine *uc; uc_context *ctx; char code[] = "\x40"; // inc eax int r_eax = 1; uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, code, sizeof(code) - 1); OK(uc_reg_write(uc, UC_X86_REG_EAX, &r_eax)); OK(uc_context_alloc(uc, &ctx)); OK(uc_context_save(uc, ctx)); OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 0)); OK(uc_reg_read(uc, UC_X86_REG_EAX, &r_eax)); TEST_CHECK(r_eax == 2); OK(uc_context_restore(uc, ctx)); OK(uc_reg_read(uc, UC_X86_REG_EAX, &r_eax)); TEST_CHECK(r_eax == 1); OK(uc_context_free(ctx)); OK(uc_close(uc)); } static bool test_x86_invalid_mem_read_stop_in_cb_callback(uc_engine *uc, uc_mem_type type, uint64_t address, int size, uint64_t value, void *user_data) { // False indicates that we fail to handle this ERROR and let the emulation // stop. // // Note that the memory must be mapped properly if we return true! Check // test_x86_mem_hook_all for example. return false; } static void test_x86_invalid_mem_read_stop_in_cb() { uc_engine *uc; uc_hook hook; char code[] = "\x40\x8b\x1d\x00\x00\x10\x00\x42"; // inc eax; mov ebx, // [0x100000]; inc edx int r_eax = 0x1234; int r_edx = 0x5678; int r_eip = 0; uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, code, sizeof(code) - 1); OK(uc_hook_add(uc, &hook, UC_HOOK_MEM_READ, test_x86_invalid_mem_read_stop_in_cb_callback, NULL, 1, 0)); OK(uc_reg_write(uc, UC_X86_REG_EAX, &r_eax)); OK(uc_reg_write(uc, UC_X86_REG_EDX, &r_edx)); uc_assert_err( UC_ERR_READ_UNMAPPED, uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 0)); // The state of Unicorn should be correct at this time. OK(uc_reg_read(uc, UC_X86_REG_EIP, &r_eip)); OK(uc_reg_read(uc, UC_X86_REG_EAX, &r_eax)); OK(uc_reg_read(uc, UC_X86_REG_EDX, &r_edx)); TEST_CHECK(r_eip == code_start + 1); TEST_CHECK(r_eax == 0x1235); TEST_CHECK(r_edx == 0x5678); OK(uc_close(uc)); } static void test_x86_x87_fnstenv_callback(uc_engine *uc, uint64_t address, uint32_t size, void *user_data) { uint32_t r_eip; uint32_t r_eax; uint32_t fnstenv[7]; if (address == code_start + 4) { // The first fnstenv executed // Save the address of the fld. OK(uc_reg_read(uc, UC_X86_REG_EIP, &r_eip)); *((uint32_t *)user_data) = r_eip; OK(uc_reg_read(uc, UC_X86_REG_EAX, &r_eax)); OK(uc_mem_read(uc, r_eax, fnstenv, sizeof(fnstenv))); // Don't update FCS:FIP for fnop. TEST_CHECK(fnstenv[3] == 0); } } static void test_x86_x87_fnstenv() { uc_engine *uc; uc_hook hook; char code[] = "\xd9\xd0\xd9\x30\xd9\x00\xd9\x30"; // fnop;fnstenv [eax];fld dword ptr // [eax];fnstenv [eax] uint32_t base = code_start + 3 * code_len; uint32_t last_eip; uint32_t fnstenv[7]; uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, code, sizeof(code) - 1); OK(uc_mem_map(uc, base, code_len, UC_PROT_ALL)); OK(uc_reg_write(uc, UC_X86_REG_EAX, &base)); OK(uc_hook_add(uc, &hook, UC_HOOK_CODE, test_x86_x87_fnstenv_callback, &last_eip, 1, 0)); OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 0)); OK(uc_mem_read(uc, base, fnstenv, sizeof(fnstenv))); // But update FCS:FIP for fld. TEST_CHECK(fnstenv[3] == last_eip); OK(uc_close(uc)); } static uint64_t test_x86_mmio_read_callback(uc_engine *uc, uint64_t offset, unsigned size, void *user_data) { TEST_CHECK(offset == 4); TEST_CHECK(size == 4); return 0x19260817; } static void test_x86_mmio_write_callback(uc_engine *uc, uint64_t offset, unsigned size, uint64_t value, void *user_data) { TEST_CHECK(offset == 4); TEST_CHECK(size == 4); TEST_CHECK(value == 0xdeadbeef); return; } static void test_x86_mmio() { uc_engine *uc; int r_ecx = 0xdeadbeef; char code[] = "\x89\x0d\x04\x00\x02\x00\x8b\x0d\x04\x00\x02\x00"; // mov [0x20004], // ecx; mov ecx, // [0x20004] uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, code, sizeof(code) - 1); OK(uc_reg_write(uc, UC_X86_REG_ECX, &r_ecx)); OK(uc_mmio_map(uc, 0x20000, 0x1000, test_x86_mmio_read_callback, NULL, test_x86_mmio_write_callback, NULL)); OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 0)); OK(uc_reg_read(uc, UC_X86_REG_ECX, &r_ecx)); TEST_CHECK(r_ecx == 0x19260817); OK(uc_close(uc)); } static bool test_x86_missing_code_callback(uc_engine *uc, uc_mem_type type, uint64_t address, int size, uint64_t value, void *user_data) { char code[] = "\x41\x4a"; // inc ecx; dec edx; uint64_t algined_address = address & 0xFFFFFFFFFFFFF000ULL; int aligned_size = ((int)(size / 0x1000) + 1) * 0x1000; OK(uc_mem_map(uc, algined_address, aligned_size, UC_PROT_ALL)); OK(uc_mem_write(uc, algined_address, code, sizeof(code) - 1)); return true; } static void test_x86_missing_code() { uc_engine *uc; uc_hook hook; int r_ecx = 0x1234; int r_edx = 0x7890; // Don't write any code by design. OK(uc_open(UC_ARCH_X86, UC_MODE_32, &uc)); OK(uc_reg_write(uc, UC_X86_REG_ECX, &r_ecx)); OK(uc_reg_write(uc, UC_X86_REG_EDX, &r_edx)); OK(uc_hook_add(uc, &hook, UC_HOOK_MEM_UNMAPPED, test_x86_missing_code_callback, NULL, 1, 0)); OK(uc_emu_start(uc, code_start, code_start + 2, 0, 0)); OK(uc_reg_read(uc, UC_X86_REG_ECX, &r_ecx)); OK(uc_reg_read(uc, UC_X86_REG_EDX, &r_edx)); TEST_CHECK(r_ecx == 0x1235); TEST_CHECK(r_edx == 0x788f); OK(uc_close(uc)); } static void test_x86_smc_xor() { uc_engine *uc; /* * 0x1000 xor dword ptr [edi+0x3], eax ; edi=0x1000, eax=0xbc4177e6 * 0x1003 dw 0x3ea98b13 */ char code[] = "\x31\x47\x03\x13\x8b\xa9\x3e"; int r_edi = code_start; int r_eax = 0xbc4177e6; uint32_t result; uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, code, sizeof(code) - 1); uc_reg_write(uc, UC_X86_REG_EDI, &r_edi); uc_reg_write(uc, UC_X86_REG_EAX, &r_eax); OK(uc_emu_start(uc, code_start, code_start + 3, 0, 0)); OK(uc_mem_read(uc, code_start + 3, (void *)&result, 4)); TEST_CHECK(result == (0x3ea98b13 ^ 0xbc4177e6)); OK(uc_close(uc)); } static uint64_t test_x86_mmio_uc_mem_rw_read_callback(uc_engine *uc, uint64_t offset, unsigned size, void *user_data) { TEST_CHECK(offset == 8); TEST_CHECK(size == 4); return 0x19260817; } static void test_x86_mmio_uc_mem_rw_write_callback(uc_engine *uc, uint64_t offset, unsigned size, uint64_t value, void *user_data) { TEST_CHECK(offset == 4); TEST_CHECK(size == 4); TEST_CHECK(value == 0xdeadbeef); return; } static void test_x86_mmio_uc_mem_rw() { uc_engine *uc; int data = 0xdeadbeef; OK(uc_open(UC_ARCH_X86, UC_MODE_32, &uc)); OK(uc_mmio_map(uc, 0x20000, 0x1000, test_x86_mmio_uc_mem_rw_read_callback, NULL, test_x86_mmio_uc_mem_rw_write_callback, NULL)); OK(uc_mem_write(uc, 0x20004, (void *)&data, 4)); OK(uc_mem_read(uc, 0x20008, (void *)&data, 4)); TEST_CHECK(data == 0x19260817); OK(uc_close(uc)); } static void test_x86_sysenter_hook(uc_engine *uc, void *user) { *(int *)user = 1; } static void test_x86_sysenter() { uc_engine *uc; char code[] = "\x0F\x34"; // sysenter uc_hook h; int called = 0; uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, code, sizeof(code) - 1); OK(uc_hook_add(uc, &h, UC_HOOK_INSN, test_x86_sysenter_hook, &called, 1, 0, UC_X86_INS_SYSENTER)); OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 0)); TEST_CHECK(called == 1); OK(uc_close(uc)); } static void test_x86_hook_cpuid_callback(uc_engine *uc, void *data) { int reg = 7; OK(uc_reg_write(uc, UC_X86_REG_EAX, ®)); } static void test_x86_hook_cpuid() { uc_engine *uc; char code[] = "\x40\x0F\xA2"; // INC EAX; CPUID uc_hook h; int reg; uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, code, sizeof(code) - 1); OK(uc_hook_add(uc, &h, UC_HOOK_INSN, test_x86_hook_cpuid_callback, NULL, 1, 0, UC_X86_INS_CPUID)); OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 0)); OK(uc_reg_read(uc, UC_X86_REG_EAX, ®)); TEST_CHECK(reg == 7); OK(uc_close(uc)); } TEST_LIST = {{"test_x86_in", test_x86_in}, {"test_x86_out", test_x86_out}, {"test_x86_mem_hook_all", test_x86_mem_hook_all}, {"test_x86_inc_dec_pxor", test_x86_inc_dec_pxor}, {"test_x86_relative_jump", test_x86_relative_jump}, {"test_x86_loop", test_x86_loop}, {"test_x86_invalid_mem_read", test_x86_invalid_mem_read}, {"test_x86_invalid_mem_write", test_x86_invalid_mem_write}, {"test_x86_invalid_jump", test_x86_invalid_jump}, {"test_x86_64_syscall", test_x86_64_syscall}, {"test_x86_16_add", test_x86_16_add}, {"test_x86_reg_save", test_x86_reg_save}, {"test_x86_invalid_mem_read_stop_in_cb", test_x86_invalid_mem_read_stop_in_cb}, {"test_x86_x87_fnstenv", test_x86_x87_fnstenv}, {"test_x86_mmio", test_x86_mmio}, {"test_x86_missing_code", test_x86_missing_code}, {"test_x86_smc_xor", test_x86_smc_xor}, {"test_x86_mmio_uc_mem_rw", test_x86_mmio_uc_mem_rw}, {"test_x86_sysenter", test_x86_sysenter}, {"test_x86_hook_cpuid", test_x86_hook_cpuid}, {NULL, NULL}};