unicorn/tests/unit/test_x86.c

1275 lines
38 KiB
C

#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(void)
{
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(void)
{
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(void)
{
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(void)
{
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(void)
{
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(void)
{
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(void)
{
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(void)
{
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(void)
{
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(void)
{
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(void)
{
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(void)
{
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(void)
{
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(void)
{
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(LEINT32(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(void)
{
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(void)
{
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(void)
{
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(LEINT32(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(void)
{
uc_engine *uc;
int data = LEINT32(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(LEINT32(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(void)
{
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 int test_x86_hook_cpuid_callback(uc_engine *uc, void *data)
{
int reg = 7;
OK(uc_reg_write(uc, UC_X86_REG_EAX, &reg));
// Overwrite the cpuid instruction.
return 1;
}
static void test_x86_hook_cpuid(void)
{
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, &reg));
TEST_CHECK(reg == 7);
OK(uc_close(uc));
}
static void test_x86_486_cpuid(void)
{
uc_engine *uc;
uint32_t eax;
uint32_t ebx;
char code[] = {0x31, 0xC0, 0x0F, 0xA2}; // XOR EAX EAX; CPUID
OK(uc_open(UC_ARCH_X86, UC_MODE_32, &uc));
OK(uc_ctl_set_cpu_model(uc, UC_CPU_X86_486));
OK(uc_mem_map(uc, 0, 4 * 1024, UC_PROT_ALL));
OK(uc_mem_write(uc, 0, code, sizeof(code) / sizeof(code[0])));
OK(uc_emu_start(uc, 0, sizeof(code) / sizeof(code[0]), 0, 0));
/* Read eax after emulation */
OK(uc_reg_read(uc, UC_X86_REG_EAX, &eax));
OK(uc_reg_read(uc, UC_X86_REG_EBX, &ebx));
TEST_CHECK(eax != 0);
TEST_CHECK(ebx == 0x756e6547); // magic string "Genu" for intel cpu
OK(uc_close(uc));
}
// This is a regression bug.
static void test_x86_clear_tb_cache(void)
{
uc_engine *uc;
char code[] = "\x83\xc1\x01\x4a"; // ADD ecx, 1; DEC edx;
int r_ecx = 0x1234;
int r_edx = 0x7890;
uint64_t code_start = 0x1240; // Choose this address by design
uint64_t code_len = 0x1000;
OK(uc_open(UC_ARCH_X86, UC_MODE_32, &uc));
OK(uc_mem_map(uc, code_start & (1 << 12), code_len, UC_PROT_ALL));
OK(uc_mem_write(uc, code_start, code, sizeof(code)));
OK(uc_reg_write(uc, UC_X86_REG_ECX, &r_ecx));
OK(uc_reg_write(uc, UC_X86_REG_EDX, &r_edx));
// This emulation should take no effect at all.
OK(uc_emu_start(uc, code_start, code_start, 0, 0));
// Emulate ADD ecx, 1.
OK(uc_emu_start(uc, code_start, code_start + 3, 0, 0));
// If tb cache is not cleared, edx would be still 0x7890
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));
TEST_CHECK(r_ecx == 0x1236);
TEST_CHECK(r_edx == 0x788f);
OK(uc_close(uc));
}
static void test_x86_clear_count_cache(void)
{
uc_engine *uc;
// uc_emu_start will clear last TB when exiting so generating a tb at last
// by design
char code[] =
"\x83\xc1\x01\x4a\xeb\x00\x83\xc3\x01"; // ADD ecx, 1; DEC edx;
// jmp t;
// t:
// ADD ebx, 1
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, 0, 2));
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));
TEST_CHECK(r_ecx == 0x1236);
TEST_CHECK(r_edx == 0x788e);
OK(uc_close(uc));
}
// This is a regression bug.
static void test_x86_clear_empty_tb(void)
{
uc_engine *uc;
// lb:
// add ecx, 1;
// cmp ecx, 0;
// jz lb;
// dec edx;
char code[] = "\x83\xc1\x01\x83\xf9\x00\x74\xf8\x4a";
int r_edx = 0x7890;
uint64_t code_start = 0x1240; // Choose this address by design
uint64_t code_len = 0x1000;
OK(uc_open(UC_ARCH_X86, UC_MODE_32, &uc));
OK(uc_mem_map(uc, code_start & (1 << 12), code_len, UC_PROT_ALL));
OK(uc_mem_write(uc, code_start, code, sizeof(code)));
OK(uc_reg_write(uc, UC_X86_REG_EDX, &r_edx));
// Make sure we generate an empty tb at the exit address by stopping at dec
// edx.
OK(uc_emu_start(uc, code_start, code_start + 8, 0, 0));
// If tb cache is not cleared, edx would be still 0x7890
OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 0));
OK(uc_reg_read(uc, UC_X86_REG_EDX, &r_edx));
TEST_CHECK(r_edx == 0x788f);
OK(uc_close(uc));
}
typedef struct _HOOK_TCG_OP_RESULT {
uint64_t address;
uint64_t arg1;
uint64_t arg2;
} HOOK_TCG_OP_RESULT;
typedef struct _HOOK_TCG_OP_RESULTS {
HOOK_TCG_OP_RESULT results[128];
uint64_t len;
} HOOK_TCG_OP_RESULTS;
static void test_x86_hook_tcg_op_cb(uc_engine *uc, uint64_t address,
uint64_t arg1, uint64_t arg2, uint32_t size,
void *data)
{
HOOK_TCG_OP_RESULTS *results = (HOOK_TCG_OP_RESULTS *)data;
HOOK_TCG_OP_RESULT *result = &results->results[results->len++];
result->address = address;
result->arg1 = arg1;
result->arg2 = arg2;
}
static void test_x86_hook_tcg_op(void)
{
uc_engine *uc;
uc_hook h;
int flag;
HOOK_TCG_OP_RESULTS results;
// sub esi, [0x1000];
// sub eax, ebx;
// sub eax, 1;
// cmp eax, 0;
// cmp ebx, edx;
// cmp esi, [0x1000];
char code[] = "\x2b\x35\x00\x10\x00\x00\x29\xd8\x83\xe8\x01\x83\xf8\x00\x39"
"\xd3\x3b\x35\x00\x10\x00\x00";
int r_eax = 0x1234;
int r_ebx = 2;
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_reg_write(uc, UC_X86_REG_EBX, &r_ebx));
memset(&results, 0, sizeof(HOOK_TCG_OP_RESULTS));
flag = 0;
OK(uc_hook_add(uc, &h, UC_HOOK_TCG_OPCODE, test_x86_hook_tcg_op_cb,
&results, 0, -1, UC_TCG_OP_SUB, flag));
OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 0));
OK(uc_hook_del(uc, h));
TEST_CHECK(results.len == 6);
memset(&results, 0, sizeof(HOOK_TCG_OP_RESULTS));
flag = UC_TCG_OP_FLAG_DIRECT;
OK(uc_hook_add(uc, &h, UC_HOOK_TCG_OPCODE, test_x86_hook_tcg_op_cb,
&results, 0, -1, UC_TCG_OP_SUB, flag));
OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 0));
OK(uc_hook_del(uc, h));
TEST_CHECK(results.len == 3);
memset(&results, 0, sizeof(HOOK_TCG_OP_RESULTS));
flag = UC_TCG_OP_FLAG_CMP;
OK(uc_hook_add(uc, &h, UC_HOOK_TCG_OPCODE, test_x86_hook_tcg_op_cb,
&results, 0, -1, UC_TCG_OP_SUB, flag));
OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 0));
OK(uc_hook_del(uc, h));
TEST_CHECK(results.len == 3);
OK(uc_close(uc));
}
static bool test_x86_cmpxchg_mem_hook(uc_engine *uc, uc_mem_type type,
uint64_t address, int size, int64_t val,
void *data)
{
if (type == UC_MEM_READ) {
*((int *)data) |= 1;
} else {
*((int *)data) |= 2;
}
return true;
}
static void test_x86_cmpxchg(void)
{
uc_engine *uc;
char code[] = "\x0F\xC7\x0D\xE0\xBE\xAD\xDE"; // cmpxchg8b [0xdeadbee0]
int r_zero = 0;
int r_aaaa = 0x41414141;
uint64_t mem;
uc_hook h;
int result = 0;
uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, code, sizeof(code) - 1);
OK(uc_mem_map(uc, 0xdeadb000, 0x1000, UC_PROT_ALL));
OK(uc_hook_add(uc, &h, UC_HOOK_MEM_READ | UC_HOOK_MEM_WRITE,
test_x86_cmpxchg_mem_hook, &result, 1, 0));
OK(uc_reg_write(uc, UC_X86_REG_EDX, &r_zero));
OK(uc_reg_write(uc, UC_X86_REG_EAX, &r_zero));
OK(uc_reg_write(uc, UC_X86_REG_ECX, &r_aaaa));
OK(uc_reg_write(uc, UC_X86_REG_EBX, &r_aaaa));
OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 0));
OK(uc_mem_read(uc, 0xdeadbee0, &mem, 8));
TEST_CHECK(mem == 0x4141414141414141);
// Both read and write happened.
TEST_CHECK(result == 3);
OK(uc_close(uc));
}
static void test_x86_nested_emu_start_cb(uc_engine *uc, uint64_t addr,
size_t size, void *data)
{
OK(uc_emu_start(uc, code_start + 1, code_start + 2, 0, 0));
}
static void test_x86_nested_emu_start(void)
{
uc_engine *uc;
char code[] = "\x41\x4a"; // INC ecx; DEC edx;
int r_ecx = 0x1234;
int r_edx = 0x7890;
uc_hook h;
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));
// Emulate DEC in the nested hook.
OK(uc_hook_add(uc, &h, UC_HOOK_CODE, test_x86_nested_emu_start_cb, NULL,
code_start, code_start));
// Emulate INC
OK(uc_emu_start(uc, code_start, code_start + 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));
TEST_CHECK(r_ecx == 0x1235);
TEST_CHECK(r_edx == 0x788f);
OK(uc_close(uc));
}
static void test_x86_nested_emu_stop_cb(uc_engine *uc, uint64_t addr,
size_t size, void *data)
{
OK(uc_emu_start(uc, code_start + 1, code_start + 2, 0, 0));
// ecx shouldn't be changed!
OK(uc_emu_stop(uc));
}
static void test_x86_nested_emu_stop(void)
{
uc_engine *uc;
// INC ecx; DEC edx; DEC edx;
char code[] = "\x41\x4a\x4a";
int r_ecx = 0x1234;
int r_edx = 0x7890;
uc_hook h;
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));
// Emulate DEC in the nested hook.
OK(uc_hook_add(uc, &h, UC_HOOK_CODE, test_x86_nested_emu_stop_cb, NULL,
code_start, code_start));
OK(uc_emu_start(uc, code_start, code_start + 3, 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 == 0x1234);
TEST_CHECK(r_edx == 0x788f);
OK(uc_close(uc));
}
static void test_x86_nested_emu_start_error_cb(uc_engine *uc, uint64_t addr,
size_t size, void *data)
{
uc_assert_err(UC_ERR_READ_UNMAPPED,
uc_emu_start(uc, code_start + 2, 0, 0, 0));
}
static void test_x86_64_nested_emu_start_error(void)
{
uc_engine *uc;
// "nop;nop;mov rax, [0x10000]"
char code[] = "\x90\x90\x48\xa1\x00\x00\x01\x00\x00\x00\x00\x00";
uc_hook hk;
uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_64, code, sizeof(code) - 1);
OK(uc_hook_add(uc, &hk, UC_HOOK_CODE, test_x86_nested_emu_start_error_cb,
NULL, code_start, code_start));
// This call shouldn't fail!
OK(uc_emu_start(uc, code_start, code_start + 2, 0, 0));
OK(uc_close(uc));
}
static void test_x86_eflags_reserved_bit(void)
{
uc_engine *uc;
uint32_t r_eflags;
OK(uc_open(UC_ARCH_X86, UC_MODE_32, &uc));
OK(uc_reg_read(uc, UC_X86_REG_EFLAGS, &r_eflags));
TEST_CHECK((r_eflags & 2) != 0);
OK(uc_reg_write(uc, UC_X86_REG_EFLAGS, &r_eflags));
OK(uc_reg_read(uc, UC_X86_REG_EFLAGS, &r_eflags));
TEST_CHECK((r_eflags & 2) != 0);
OK(uc_close(uc));
}
static void test_x86_nested_uc_emu_start_exits_cb(uc_engine *uc, uint64_t addr,
size_t size, void *data)
{
OK(uc_emu_start(uc, code_start + 5, code_start + 6, 0, 0));
}
static void test_x86_nested_uc_emu_start_exits(void)
{
uc_engine *uc;
// cmp eax, 0
// jnz t
// nop <-- nested emu_start
// t:mov dword ptr [eax], 0
char code[] = "\x83\xf8\x00\x75\x01\x90\xc7\x00\x00\x00\x00\x00";
uc_hook hk;
uint32_t r_pc;
uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, code, sizeof(code) - 1);
OK(uc_hook_add(uc, &hk, UC_HOOK_CODE, test_x86_nested_uc_emu_start_exits_cb,
NULL, code_start, code_start));
OK(uc_emu_start(uc, code_start, code_start + 5, 0, 0));
OK(uc_reg_read(uc, UC_X86_REG_EIP, &r_pc));
TEST_CHECK(r_pc == code_start + 5);
OK(uc_close(uc));
}
static bool test_x86_correct_address_in_small_jump_hook_callback(
uc_engine *uc, int type, uint64_t address, int size, int64_t value,
void *user_data)
{
// Check registers
uint64_t r_rax = 0x0;
uint64_t r_rip = 0x0;
OK(uc_reg_read(uc, UC_X86_REG_RAX, &r_rax));
OK(uc_reg_read(uc, UC_X86_REG_RIP, &r_rip));
TEST_CHECK(r_rax == 0x7F00);
TEST_CHECK(r_rip == 0x7F00);
// Check address
// printf("%lx\n", address);
TEST_CHECK(address == 0x7F00);
return false;
}
static void test_x86_correct_address_in_small_jump_hook(void)
{
uc_engine *uc;
// movabs $0x7F00, %rax
// jmp *%rax
char code[] = "\x48\xb8\x00\x7F\x00\x00\x00\x00\x00\x00\xff\xe0";
uint64_t r_rax = 0x0;
uint64_t r_rip = 0x0;
uc_hook hook;
uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_64, code, sizeof(code) - 1);
OK(uc_hook_add(uc, &hook, UC_HOOK_MEM_UNMAPPED,
test_x86_correct_address_in_small_jump_hook_callback, NULL,
1, 0));
uc_assert_err(
UC_ERR_FETCH_UNMAPPED,
uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 0));
OK(uc_reg_read(uc, UC_X86_REG_RAX, &r_rax));
OK(uc_reg_read(uc, UC_X86_REG_RIP, &r_rip));
TEST_CHECK(r_rax == 0x7F00);
TEST_CHECK(r_rip == 0x7F00);
OK(uc_close(uc));
}
static bool test_x86_correct_address_in_long_jump_hook_callback(
uc_engine *uc, int type, uint64_t address, int size, int64_t value,
void *user_data)
{
// Check registers
uint64_t r_rax = 0x0;
uint64_t r_rip = 0x0;
OK(uc_reg_read(uc, UC_X86_REG_RAX, &r_rax));
OK(uc_reg_read(uc, UC_X86_REG_RIP, &r_rip));
TEST_CHECK(r_rax == 0x7FFFFFFFFFFFFF00);
TEST_CHECK(r_rip == 0x7FFFFFFFFFFFFF00);
// Check address
// printf("%lx\n", address);
TEST_CHECK(address == 0x7FFFFFFFFFFFFF00);
return false;
}
static void test_x86_correct_address_in_long_jump_hook(void)
{
uc_engine *uc;
// movabs $0x7FFFFFFFFFFFFF00, %rax
// jmp *%rax
char code[] = "\x48\xb8\x00\xff\xff\xff\xff\xff\xff\x7f\xff\xe0";
uint64_t r_rax = 0x0;
uint64_t r_rip = 0x0;
uc_hook hook;
uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_64, code, sizeof(code) - 1);
OK(uc_hook_add(uc, &hook, UC_HOOK_MEM_UNMAPPED,
test_x86_correct_address_in_long_jump_hook_callback, NULL, 1,
0));
uc_assert_err(
UC_ERR_FETCH_UNMAPPED,
uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 0));
OK(uc_reg_read(uc, UC_X86_REG_RAX, &r_rax));
OK(uc_reg_read(uc, UC_X86_REG_RIP, &r_rip));
TEST_CHECK(r_rax == 0x7FFFFFFFFFFFFF00);
TEST_CHECK(r_rip == 0x7FFFFFFFFFFFFF00);
OK(uc_close(uc));
}
static void test_x86_invalid_vex_l(void)
{
uc_engine *uc;
/* vmovdqu ymm1, [rcx] */
char code[] = {'\xC5', '\xFE', '\x6F', '\x09'};
/* initialize memory and run emulation */
OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc));
OK(uc_mem_map(uc, 0, 2 * 1024 * 1024, UC_PROT_ALL));
OK(uc_mem_write(uc, 0, code, sizeof(code) / sizeof(code[0])));
uc_assert_err(UC_ERR_INSN_INVALID,
uc_emu_start(uc, 0, sizeof(code) / sizeof(code[0]), 0, 0));
OK(uc_close(uc));
}
// AARCH64 inline the read while s390x won't split the access. Though not tested on other hosts
// but we restrict a bit more.
#if !defined(TARGET_READ_INLINED) && defined(BOOST_LITTLE_ENDIAN)
struct writelog_t {
uint32_t addr, size;
};
static void test_x86_unaligned_access_callback(uc_engine *uc, uc_mem_type type,
uint64_t address, int size,
int64_t value, void *user_data)
{
TEST_CHECK(size != 0);
struct writelog_t *write_log = (struct writelog_t *)user_data;
for (int i = 0; i < 10; i++) {
if (write_log[i].size == 0) {
write_log[i].addr = (uint32_t)address;
write_log[i].size = (uint32_t)size;
return;
}
}
TEST_ASSERT(false);
}
static void test_x86_unaligned_access(void)
{
uc_engine *uc;
uc_hook hook;
// mov dword ptr [0x200001], eax; mov eax, dword ptr [0x200001]
char code[] = "\xa3\x01\x00\x20\x00\xa1\x01\x00\x20\x00";
uint32_t r_eax = LEINT32(0x41424344);
struct writelog_t write_log[10];
struct writelog_t read_log[10];
memset(write_log, 0, sizeof(write_log));
memset(read_log, 0, sizeof(read_log));
uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_32, code, sizeof(code) - 1);
OK(uc_mem_map(uc, 0x200000, 0x1000, UC_PROT_ALL));
OK(uc_hook_add(uc, &hook, UC_HOOK_MEM_WRITE,
test_x86_unaligned_access_callback, write_log, 1, 0));
OK(uc_hook_add(uc, &hook, UC_HOOK_MEM_READ,
test_x86_unaligned_access_callback, read_log, 1, 0));
OK(uc_reg_write(uc, UC_X86_REG_EAX, &r_eax));
OK(uc_emu_start(uc, code_start, code_start + sizeof(code) - 1, 0, 0));
TEST_CHECK(write_log[0].addr == 0x200001);
TEST_CHECK(write_log[0].size == 4);
TEST_CHECK(write_log[1].size == 0);
TEST_CHECK(read_log[0].addr == 0x200001);
TEST_CHECK(read_log[0].size == 4);
TEST_CHECK(read_log[1].size == 0);
char b;
OK(uc_mem_read(uc, 0x200001, &b, 1));
TEST_CHECK(b == 0x44);
OK(uc_mem_read(uc, 0x200002, &b, 1));
TEST_CHECK(b == 0x43);
OK(uc_mem_read(uc, 0x200003, &b, 1));
TEST_CHECK(b == 0x42);
OK(uc_mem_read(uc, 0x200004, &b, 1));
TEST_CHECK(b == 0x41);
OK(uc_close(uc));
}
#endif
static bool test_x86_lazy_mapping_mem_callback(uc_engine *uc, uc_mem_type type,
uint64_t address, int size,
int64_t value, void *user_data)
{
OK(uc_mem_map(uc, 0x1000, 0x1000, UC_PROT_ALL));
OK(uc_mem_write(uc, 0x1000, "\x90\x90", 2)); // nop; nop
// Handled!
return true;
}
static void test_x86_lazy_mapping_block_callback(uc_engine *uc,
uint64_t address,
uint32_t size, void *user_data)
{
int *block_count = (int *)user_data;
(*block_count)++;
}
static void test_x86_lazy_mapping(void)
{
uc_engine *uc;
uc_hook mem_hook, block_hook;
int block_count = 0;
OK(uc_open(UC_ARCH_X86, UC_MODE_32, &uc));
OK(uc_hook_add(uc, &mem_hook, UC_HOOK_MEM_FETCH_UNMAPPED,
test_x86_lazy_mapping_mem_callback, NULL, 1, 0));
OK(uc_hook_add(uc, &block_hook, UC_HOOK_BLOCK,
test_x86_lazy_mapping_block_callback, &block_count, 1, 0));
OK(uc_emu_start(uc, 0x1000, 0x1002, 0, 0));
TEST_CHECK(block_count == 1);
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},
{"test_x86_486_cpuid", test_x86_486_cpuid},
{"test_x86_clear_tb_cache", test_x86_clear_tb_cache},
{"test_x86_clear_empty_tb", test_x86_clear_empty_tb},
{"test_x86_hook_tcg_op", test_x86_hook_tcg_op},
{"test_x86_cmpxchg", test_x86_cmpxchg},
{"test_x86_nested_emu_start", test_x86_nested_emu_start},
{"test_x86_nested_emu_stop", test_x86_nested_emu_stop},
{"test_x86_64_nested_emu_start_error", test_x86_64_nested_emu_start_error},
{"test_x86_eflags_reserved_bit", test_x86_eflags_reserved_bit},
{"test_x86_nested_uc_emu_start_exits", test_x86_nested_uc_emu_start_exits},
{"test_x86_clear_count_cache", test_x86_clear_count_cache},
{"test_x86_correct_address_in_small_jump_hook",
test_x86_correct_address_in_small_jump_hook},
{"test_x86_correct_address_in_long_jump_hook",
test_x86_correct_address_in_long_jump_hook},
{"test_x86_invalid_vex_l", test_x86_invalid_vex_l},
#if !defined(TARGET_READ_INLINED) && defined(BOOST_LITTLE_ENDIAN)
{"test_x86_unaligned_access", test_x86_unaligned_access},
#endif
{"test_x86_lazy_mapping", test_x86_lazy_mapping},
{NULL, NULL}};