make i386 instructions RDTSC and RDTSCP hookable (#2066)
* instruction hooks for RDTSC and RDTSCP Signed-off-by: Pedro Tôrres <t0rr3sp3dr0@gmail.com> * update hookable instruction list Signed-off-by: Pedro Tôrres <t0rr3sp3dr0@gmail.com> * test RDTSC and RDTSCP instruction hooks Signed-off-by: Pedro Tôrres <t0rr3sp3dr0@gmail.com> --------- Signed-off-by: Pedro Tôrres <t0rr3sp3dr0@gmail.com> Co-authored-by: mio <mio@lazym.io>
This commit is contained in:
@@ -40,3 +40,7 @@ void hookX86Out_cgo(uc_engine *handle, uint32_t port, uint32_t size, uint32_t va
|
|||||||
void hookX86Syscall_cgo(uc_engine *handle, uintptr_t user) {
|
void hookX86Syscall_cgo(uc_engine *handle, uintptr_t user) {
|
||||||
hookX86Syscall(handle, (void *)user);
|
hookX86Syscall(handle, (void *)user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int hookX86Cpuid_cgo(uc_engine *handle, uintptr_t user) {
|
||||||
|
return hookX86Cpuid(handle, (void *)user);
|
||||||
|
}
|
||||||
|
|||||||
@@ -98,6 +98,12 @@ func hookX86Syscall(handle unsafe.Pointer, user unsafe.Pointer) {
|
|||||||
hook.Callback.(func(Unicorn))(hook.Uc)
|
hook.Callback.(func(Unicorn))(hook.Uc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//export hookX86Cpuid
|
||||||
|
func hookX86Cpuid(handle unsafe.Pointer, user unsafe.Pointer) bool {
|
||||||
|
hook := hookMap.get(user)
|
||||||
|
return hook.Callback.(func(Unicorn) bool)(hook.Uc)
|
||||||
|
}
|
||||||
|
|
||||||
func (u *uc) HookAdd(htype int, cb interface{}, begin, end uint64, extra ...int) (Hook, error) {
|
func (u *uc) HookAdd(htype int, cb interface{}, begin, end uint64, extra ...int) (Hook, error) {
|
||||||
var callback unsafe.Pointer
|
var callback unsafe.Pointer
|
||||||
var insn C.int
|
var insn C.int
|
||||||
@@ -119,6 +125,8 @@ func (u *uc) HookAdd(htype int, cb interface{}, begin, end uint64, extra ...int)
|
|||||||
callback = C.hookX86Out_cgo
|
callback = C.hookX86Out_cgo
|
||||||
case X86_INS_SYSCALL, X86_INS_SYSENTER:
|
case X86_INS_SYSCALL, X86_INS_SYSENTER:
|
||||||
callback = C.hookX86Syscall_cgo
|
callback = C.hookX86Syscall_cgo
|
||||||
|
case X86_INS_CPUID, X86_INS_RDTSC, X86_INS_RDTSCP:
|
||||||
|
callback = C.hookX86Cpuid_cgo
|
||||||
default:
|
default:
|
||||||
return 0, errors.New("Unknown instruction type.")
|
return 0, errors.New("Unknown instruction type.")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,3 +7,4 @@ void hookInterrupt_cgo(uc_engine *handle, uint32_t intno, uintptr_t user);
|
|||||||
uint32_t hookX86In_cgo(uc_engine *handle, uint32_t port, uint32_t size, uintptr_t user);
|
uint32_t hookX86In_cgo(uc_engine *handle, uint32_t port, uint32_t size, uintptr_t user);
|
||||||
void hookX86Out_cgo(uc_engine *handle, uint32_t port, uint32_t size, uint32_t value, uintptr_t user);
|
void hookX86Out_cgo(uc_engine *handle, uint32_t port, uint32_t size, uint32_t value, uintptr_t user);
|
||||||
void hookX86Syscall_cgo(uc_engine *handle, uintptr_t user);
|
void hookX86Syscall_cgo(uc_engine *handle, uintptr_t user);
|
||||||
|
int hookX86Cpuid_cgo(uc_engine *handle, uintptr_t user);
|
||||||
|
|||||||
@@ -209,21 +209,79 @@ void helper_invlpg(CPUX86State *env, target_ulong addr)
|
|||||||
void helper_rdtsc(CPUX86State *env)
|
void helper_rdtsc(CPUX86State *env)
|
||||||
{
|
{
|
||||||
uint64_t val;
|
uint64_t val;
|
||||||
|
uc_engine *uc = env->uc;
|
||||||
|
struct hook *hook;
|
||||||
|
int skip_rdtsc = 0;
|
||||||
|
|
||||||
if ((env->cr[4] & CR4_TSD_MASK) && ((env->hflags & HF_CPL_MASK) != 0)) {
|
if ((env->cr[4] & CR4_TSD_MASK) && ((env->hflags & HF_CPL_MASK) != 0)) {
|
||||||
raise_exception_ra(env, EXCP0D_GPF, GETPC());
|
raise_exception_ra(env, EXCP0D_GPF, GETPC());
|
||||||
}
|
}
|
||||||
cpu_svm_check_intercept_param(env, SVM_EXIT_RDTSC, 0, GETPC());
|
cpu_svm_check_intercept_param(env, SVM_EXIT_RDTSC, 0, GETPC());
|
||||||
|
|
||||||
val = cpu_get_tsc(env) + env->tsc_offset;
|
// Unicorn: call registered RDTSC hooks
|
||||||
env->regs[R_EAX] = (uint32_t)(val);
|
HOOK_FOREACH_VAR_DECLARE;
|
||||||
env->regs[R_EDX] = (uint32_t)(val >> 32);
|
HOOK_FOREACH(env->uc, hook, UC_HOOK_INSN) {
|
||||||
|
if (hook->to_delete)
|
||||||
|
continue;
|
||||||
|
if (!HOOK_BOUND_CHECK(hook, env->eip))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Multiple rdtsc callbacks returning different values is undefined.
|
||||||
|
// true -> skip the rdtsc instruction
|
||||||
|
if (hook->insn == UC_X86_INS_RDTSC) {
|
||||||
|
JIT_CALLBACK_GUARD_VAR(skip_rdtsc, ((uc_cb_insn_cpuid_t)hook->callback)(env->uc, hook->user_data));
|
||||||
|
}
|
||||||
|
|
||||||
|
// the last callback may already asked to stop emulation
|
||||||
|
if (env->uc->stop_request)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!skip_rdtsc) {
|
||||||
|
val = cpu_get_tsc(env) + env->tsc_offset;
|
||||||
|
env->regs[R_EAX] = (uint32_t)(val);
|
||||||
|
env->regs[R_EDX] = (uint32_t)(val >> 32);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void helper_rdtscp(CPUX86State *env)
|
void helper_rdtscp(CPUX86State *env)
|
||||||
{
|
{
|
||||||
helper_rdtsc(env);
|
uint64_t val;
|
||||||
env->regs[R_ECX] = (uint32_t)(env->tsc_aux);
|
uc_engine *uc = env->uc;
|
||||||
|
struct hook *hook;
|
||||||
|
int skip_rdtscp = 0;
|
||||||
|
|
||||||
|
if ((env->cr[4] & CR4_TSD_MASK) && ((env->hflags & HF_CPL_MASK) != 0)) {
|
||||||
|
raise_exception_ra(env, EXCP0D_GPF, GETPC());
|
||||||
|
}
|
||||||
|
cpu_svm_check_intercept_param(env, SVM_EXIT_RDTSC, 0, GETPC());
|
||||||
|
|
||||||
|
// Unicorn: call registered RDTSCP hooks
|
||||||
|
HOOK_FOREACH_VAR_DECLARE;
|
||||||
|
HOOK_FOREACH(env->uc, hook, UC_HOOK_INSN) {
|
||||||
|
if (hook->to_delete)
|
||||||
|
continue;
|
||||||
|
if (!HOOK_BOUND_CHECK(hook, env->eip))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Multiple rdtscp callbacks returning different values is undefined.
|
||||||
|
// true -> skip the rdtscp instruction
|
||||||
|
if (hook->insn == UC_X86_INS_RDTSCP) {
|
||||||
|
JIT_CALLBACK_GUARD_VAR(skip_rdtscp, ((uc_cb_insn_cpuid_t)hook->callback)(env->uc, hook->user_data));
|
||||||
|
}
|
||||||
|
|
||||||
|
// the last callback may already asked to stop emulation
|
||||||
|
if (env->uc->stop_request)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!skip_rdtscp) {
|
||||||
|
val = cpu_get_tsc(env) + env->tsc_offset;
|
||||||
|
env->regs[R_EAX] = (uint32_t)(val);
|
||||||
|
env->regs[R_EDX] = (uint32_t)(val >> 32);
|
||||||
|
|
||||||
|
env->regs[R_ECX] = (uint32_t)(env->tsc_aux);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void helper_rdpmc(CPUX86State *env)
|
void helper_rdpmc(CPUX86State *env)
|
||||||
|
|||||||
@@ -2000,10 +2000,11 @@ static bool x86_stop_interrupt(struct uc_struct *uc, int intno)
|
|||||||
|
|
||||||
static bool x86_insn_hook_validate(uint32_t insn_enum)
|
static bool x86_insn_hook_validate(uint32_t insn_enum)
|
||||||
{
|
{
|
||||||
// for x86 we can only hook IN, OUT, and SYSCALL
|
// for x86 we can only hook IN, OUT, SYSCALL, SYSENTER, CPUID, RDTSC, and RDTSCP
|
||||||
if (insn_enum != UC_X86_INS_IN && insn_enum != UC_X86_INS_OUT &&
|
if (insn_enum != UC_X86_INS_IN && insn_enum != UC_X86_INS_OUT &&
|
||||||
insn_enum != UC_X86_INS_SYSCALL && insn_enum != UC_X86_INS_SYSENTER &&
|
insn_enum != UC_X86_INS_SYSCALL && insn_enum != UC_X86_INS_SYSENTER &&
|
||||||
insn_enum != UC_X86_INS_CPUID) {
|
insn_enum != UC_X86_INS_CPUID && insn_enum != UC_X86_INS_RDTSC &&
|
||||||
|
insn_enum != UC_X86_INS_RDTSCP) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -1891,6 +1891,92 @@ static void test_x86_ro_segfault(void)
|
|||||||
OK(uc_close(uc));
|
OK(uc_close(uc));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool test_x86_hook_insn_rdtsc_cb(uc_engine *uc, void *user_data)
|
||||||
|
{
|
||||||
|
uint64_t h = 0x00000000FEDCBA98;
|
||||||
|
OK(uc_reg_write(uc, UC_X86_REG_RDX, &h));
|
||||||
|
|
||||||
|
uint64_t l = 0x0000000076543210;
|
||||||
|
OK(uc_reg_write(uc, UC_X86_REG_RAX, &l));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_x86_hook_insn_rdtsc(void)
|
||||||
|
{
|
||||||
|
char code[] = "\x0F\x31"; // RDTSC
|
||||||
|
|
||||||
|
uc_engine *uc;
|
||||||
|
uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_64, code, sizeof code - 1);
|
||||||
|
|
||||||
|
uc_hook hook;
|
||||||
|
OK(uc_hook_add(uc, &hook, UC_HOOK_INSN, test_x86_hook_insn_rdtsc_cb, NULL,
|
||||||
|
1, 0, UC_X86_INS_RDTSC));
|
||||||
|
|
||||||
|
OK(uc_emu_start(uc, code_start, code_start + sizeof code - 1, 0, 0));
|
||||||
|
|
||||||
|
OK(uc_hook_del(uc, hook));
|
||||||
|
|
||||||
|
uint64_t h = 0;
|
||||||
|
OK(uc_reg_read(uc, UC_X86_REG_RDX, &h));
|
||||||
|
TEST_CHECK(h == 0x00000000FEDCBA98);
|
||||||
|
|
||||||
|
uint64_t l = 0;
|
||||||
|
OK(uc_reg_read(uc, UC_X86_REG_RAX, &l));
|
||||||
|
TEST_CHECK(l == 0x0000000076543210);
|
||||||
|
|
||||||
|
OK(uc_close(uc));
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool test_x86_hook_insn_rdtscp_cb(uc_engine *uc, void *user_data)
|
||||||
|
{
|
||||||
|
uint64_t h = 0x0000000001234567;
|
||||||
|
OK(uc_reg_write(uc, UC_X86_REG_RDX, &h));
|
||||||
|
|
||||||
|
uint64_t l = 0x0000000089ABCDEF;
|
||||||
|
OK(uc_reg_write(uc, UC_X86_REG_RAX, &l));
|
||||||
|
|
||||||
|
uint64_t i = 0x00000000DEADBEEF;
|
||||||
|
OK(uc_reg_write(uc, UC_X86_REG_RCX, &i));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_x86_hook_insn_rdtscp(void)
|
||||||
|
{
|
||||||
|
uc_engine *uc;
|
||||||
|
OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc));
|
||||||
|
|
||||||
|
OK(uc_ctl_set_cpu_model(uc, UC_CPU_X86_HASWELL));
|
||||||
|
|
||||||
|
OK(uc_mem_map(uc, code_start, code_len, UC_PROT_ALL));
|
||||||
|
|
||||||
|
char code[] = "\x0F\x01\xF9"; // RDTSCP
|
||||||
|
OK(uc_mem_write(uc, code_start, code, sizeof code - 1));
|
||||||
|
|
||||||
|
uc_hook hook;
|
||||||
|
OK(uc_hook_add(uc, &hook, UC_HOOK_INSN, test_x86_hook_insn_rdtscp_cb, NULL,
|
||||||
|
1, 0, UC_X86_INS_RDTSCP));
|
||||||
|
|
||||||
|
OK(uc_emu_start(uc, code_start, code_start + sizeof code - 1, 0, 0));
|
||||||
|
|
||||||
|
OK(uc_hook_del(uc, hook));
|
||||||
|
|
||||||
|
uint64_t h = 0;
|
||||||
|
OK(uc_reg_read(uc, UC_X86_REG_RDX, &h));
|
||||||
|
TEST_CHECK(h == 0x0000000001234567);
|
||||||
|
|
||||||
|
uint64_t l = 0;
|
||||||
|
OK(uc_reg_read(uc, UC_X86_REG_RAX, &l));
|
||||||
|
TEST_CHECK(l == 0x0000000089ABCDEF);
|
||||||
|
|
||||||
|
uint64_t i = 0;
|
||||||
|
OK(uc_reg_read(uc, UC_X86_REG_RCX, &i));
|
||||||
|
TEST_CHECK(i == 0x00000000DEADBEEF);
|
||||||
|
|
||||||
|
OK(uc_close(uc));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_LIST = {
|
TEST_LIST = {
|
||||||
{"test_x86_in", test_x86_in},
|
{"test_x86_in", test_x86_in},
|
||||||
{"test_x86_out", test_x86_out},
|
{"test_x86_out", test_x86_out},
|
||||||
@@ -1947,4 +2033,6 @@ TEST_LIST = {
|
|||||||
{"test_bswap_x64", test_bswap_ax},
|
{"test_bswap_x64", test_bswap_ax},
|
||||||
{"test_rex_x64", test_rex_x64},
|
{"test_rex_x64", test_rex_x64},
|
||||||
{"test_x86_ro_segfault", test_x86_ro_segfault},
|
{"test_x86_ro_segfault", test_x86_ro_segfault},
|
||||||
|
{"test_x86_hook_insn_rdtsc", test_x86_hook_insn_rdtsc},
|
||||||
|
{"test_x86_hook_insn_rdtscp", test_x86_hook_insn_rdtscp},
|
||||||
{NULL, NULL}};
|
{NULL, NULL}};
|
||||||
|
|||||||
Reference in New Issue
Block a user