Skip to content

Commit 7737e7b

Browse files
t0rr3sp3dr0wtdcode
andauthored
make i386 instructions RDTSC and RDTSCP hookable (#2066)
* instruction hooks for RDTSC and RDTSCP Signed-off-by: Pedro Tôrres <[email protected]> * update hookable instruction list Signed-off-by: Pedro Tôrres <[email protected]> * test RDTSC and RDTSCP instruction hooks Signed-off-by: Pedro Tôrres <[email protected]> --------- Signed-off-by: Pedro Tôrres <[email protected]> Co-authored-by: mio <[email protected]>
1 parent 1cbb7b4 commit 7737e7b

File tree

6 files changed

+167
-7
lines changed

6 files changed

+167
-7
lines changed

bindings/go/unicorn/hook.c

+4
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,7 @@ void hookX86Out_cgo(uc_engine *handle, uint32_t port, uint32_t size, uint32_t va
4040
void hookX86Syscall_cgo(uc_engine *handle, uintptr_t user) {
4141
hookX86Syscall(handle, (void *)user);
4242
}
43+
44+
int hookX86Cpuid_cgo(uc_engine *handle, uintptr_t user) {
45+
return hookX86Cpuid(handle, (void *)user);
46+
}

bindings/go/unicorn/hook.go

+8
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,12 @@ func hookX86Syscall(handle unsafe.Pointer, user unsafe.Pointer) {
9898
hook.Callback.(func(Unicorn))(hook.Uc)
9999
}
100100

101+
//export hookX86Cpuid
102+
func hookX86Cpuid(handle unsafe.Pointer, user unsafe.Pointer) bool {
103+
hook := hookMap.get(user)
104+
return hook.Callback.(func(Unicorn) bool)(hook.Uc)
105+
}
106+
101107
func (u *uc) HookAdd(htype int, cb interface{}, begin, end uint64, extra ...int) (Hook, error) {
102108
var callback unsafe.Pointer
103109
var insn C.int
@@ -119,6 +125,8 @@ func (u *uc) HookAdd(htype int, cb interface{}, begin, end uint64, extra ...int)
119125
callback = C.hookX86Out_cgo
120126
case X86_INS_SYSCALL, X86_INS_SYSENTER:
121127
callback = C.hookX86Syscall_cgo
128+
case X86_INS_CPUID, X86_INS_RDTSC, X86_INS_RDTSCP:
129+
callback = C.hookX86Cpuid_cgo
122130
default:
123131
return 0, errors.New("Unknown instruction type.")
124132
}

bindings/go/unicorn/hook.h

+1
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ void hookInterrupt_cgo(uc_engine *handle, uint32_t intno, uintptr_t user);
77
uint32_t hookX86In_cgo(uc_engine *handle, uint32_t port, uint32_t size, uintptr_t user);
88
void hookX86Out_cgo(uc_engine *handle, uint32_t port, uint32_t size, uint32_t value, uintptr_t user);
99
void hookX86Syscall_cgo(uc_engine *handle, uintptr_t user);
10+
int hookX86Cpuid_cgo(uc_engine *handle, uintptr_t user);

qemu/target/i386/misc_helper.c

+63-5
Original file line numberDiff line numberDiff line change
@@ -209,21 +209,79 @@ void helper_invlpg(CPUX86State *env, target_ulong addr)
209209
void helper_rdtsc(CPUX86State *env)
210210
{
211211
uint64_t val;
212+
uc_engine *uc = env->uc;
213+
struct hook *hook;
214+
int skip_rdtsc = 0;
212215

213216
if ((env->cr[4] & CR4_TSD_MASK) && ((env->hflags & HF_CPL_MASK) != 0)) {
214217
raise_exception_ra(env, EXCP0D_GPF, GETPC());
215218
}
216219
cpu_svm_check_intercept_param(env, SVM_EXIT_RDTSC, 0, GETPC());
217220

218-
val = cpu_get_tsc(env) + env->tsc_offset;
219-
env->regs[R_EAX] = (uint32_t)(val);
220-
env->regs[R_EDX] = (uint32_t)(val >> 32);
221+
// Unicorn: call registered RDTSC hooks
222+
HOOK_FOREACH_VAR_DECLARE;
223+
HOOK_FOREACH(env->uc, hook, UC_HOOK_INSN) {
224+
if (hook->to_delete)
225+
continue;
226+
if (!HOOK_BOUND_CHECK(hook, env->eip))
227+
continue;
228+
229+
// Multiple rdtsc callbacks returning different values is undefined.
230+
// true -> skip the rdtsc instruction
231+
if (hook->insn == UC_X86_INS_RDTSC) {
232+
JIT_CALLBACK_GUARD_VAR(skip_rdtsc, ((uc_cb_insn_cpuid_t)hook->callback)(env->uc, hook->user_data));
233+
}
234+
235+
// the last callback may already asked to stop emulation
236+
if (env->uc->stop_request)
237+
break;
238+
}
239+
240+
if (!skip_rdtsc) {
241+
val = cpu_get_tsc(env) + env->tsc_offset;
242+
env->regs[R_EAX] = (uint32_t)(val);
243+
env->regs[R_EDX] = (uint32_t)(val >> 32);
244+
}
221245
}
222246

223247
void helper_rdtscp(CPUX86State *env)
224248
{
225-
helper_rdtsc(env);
226-
env->regs[R_ECX] = (uint32_t)(env->tsc_aux);
249+
uint64_t val;
250+
uc_engine *uc = env->uc;
251+
struct hook *hook;
252+
int skip_rdtscp = 0;
253+
254+
if ((env->cr[4] & CR4_TSD_MASK) && ((env->hflags & HF_CPL_MASK) != 0)) {
255+
raise_exception_ra(env, EXCP0D_GPF, GETPC());
256+
}
257+
cpu_svm_check_intercept_param(env, SVM_EXIT_RDTSC, 0, GETPC());
258+
259+
// Unicorn: call registered RDTSCP hooks
260+
HOOK_FOREACH_VAR_DECLARE;
261+
HOOK_FOREACH(env->uc, hook, UC_HOOK_INSN) {
262+
if (hook->to_delete)
263+
continue;
264+
if (!HOOK_BOUND_CHECK(hook, env->eip))
265+
continue;
266+
267+
// Multiple rdtscp callbacks returning different values is undefined.
268+
// true -> skip the rdtscp instruction
269+
if (hook->insn == UC_X86_INS_RDTSCP) {
270+
JIT_CALLBACK_GUARD_VAR(skip_rdtscp, ((uc_cb_insn_cpuid_t)hook->callback)(env->uc, hook->user_data));
271+
}
272+
273+
// the last callback may already asked to stop emulation
274+
if (env->uc->stop_request)
275+
break;
276+
}
277+
278+
if (!skip_rdtscp) {
279+
val = cpu_get_tsc(env) + env->tsc_offset;
280+
env->regs[R_EAX] = (uint32_t)(val);
281+
env->regs[R_EDX] = (uint32_t)(val >> 32);
282+
283+
env->regs[R_ECX] = (uint32_t)(env->tsc_aux);
284+
}
227285
}
228286

229287
void helper_rdpmc(CPUX86State *env)

qemu/target/i386/unicorn.c

+3-2
Original file line numberDiff line numberDiff line change
@@ -2000,10 +2000,11 @@ static bool x86_stop_interrupt(struct uc_struct *uc, int intno)
20002000

20012001
static bool x86_insn_hook_validate(uint32_t insn_enum)
20022002
{
2003-
// for x86 we can only hook IN, OUT, and SYSCALL
2003+
// for x86 we can only hook IN, OUT, SYSCALL, SYSENTER, CPUID, RDTSC, and RDTSCP
20042004
if (insn_enum != UC_X86_INS_IN && insn_enum != UC_X86_INS_OUT &&
20052005
insn_enum != UC_X86_INS_SYSCALL && insn_enum != UC_X86_INS_SYSENTER &&
2006-
insn_enum != UC_X86_INS_CPUID) {
2006+
insn_enum != UC_X86_INS_CPUID && insn_enum != UC_X86_INS_RDTSC &&
2007+
insn_enum != UC_X86_INS_RDTSCP) {
20072008
return false;
20082009
}
20092010
return true;

tests/unit/test_x86.c

+88
Original file line numberDiff line numberDiff line change
@@ -1891,6 +1891,92 @@ static void test_x86_ro_segfault(void)
18911891
OK(uc_close(uc));
18921892
}
18931893

1894+
static bool test_x86_hook_insn_rdtsc_cb(uc_engine *uc, void *user_data)
1895+
{
1896+
uint64_t h = 0x00000000FEDCBA98;
1897+
OK(uc_reg_write(uc, UC_X86_REG_RDX, &h));
1898+
1899+
uint64_t l = 0x0000000076543210;
1900+
OK(uc_reg_write(uc, UC_X86_REG_RAX, &l));
1901+
1902+
return true;
1903+
}
1904+
1905+
static void test_x86_hook_insn_rdtsc(void)
1906+
{
1907+
char code[] = "\x0F\x31"; // RDTSC
1908+
1909+
uc_engine *uc;
1910+
uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_64, code, sizeof code - 1);
1911+
1912+
uc_hook hook;
1913+
OK(uc_hook_add(uc, &hook, UC_HOOK_INSN, test_x86_hook_insn_rdtsc_cb, NULL,
1914+
1, 0, UC_X86_INS_RDTSC));
1915+
1916+
OK(uc_emu_start(uc, code_start, code_start + sizeof code - 1, 0, 0));
1917+
1918+
OK(uc_hook_del(uc, hook));
1919+
1920+
uint64_t h = 0;
1921+
OK(uc_reg_read(uc, UC_X86_REG_RDX, &h));
1922+
TEST_CHECK(h == 0x00000000FEDCBA98);
1923+
1924+
uint64_t l = 0;
1925+
OK(uc_reg_read(uc, UC_X86_REG_RAX, &l));
1926+
TEST_CHECK(l == 0x0000000076543210);
1927+
1928+
OK(uc_close(uc));
1929+
}
1930+
1931+
static bool test_x86_hook_insn_rdtscp_cb(uc_engine *uc, void *user_data)
1932+
{
1933+
uint64_t h = 0x0000000001234567;
1934+
OK(uc_reg_write(uc, UC_X86_REG_RDX, &h));
1935+
1936+
uint64_t l = 0x0000000089ABCDEF;
1937+
OK(uc_reg_write(uc, UC_X86_REG_RAX, &l));
1938+
1939+
uint64_t i = 0x00000000DEADBEEF;
1940+
OK(uc_reg_write(uc, UC_X86_REG_RCX, &i));
1941+
1942+
return true;
1943+
}
1944+
1945+
static void test_x86_hook_insn_rdtscp(void)
1946+
{
1947+
uc_engine *uc;
1948+
OK(uc_open(UC_ARCH_X86, UC_MODE_64, &uc));
1949+
1950+
OK(uc_ctl_set_cpu_model(uc, UC_CPU_X86_HASWELL));
1951+
1952+
OK(uc_mem_map(uc, code_start, code_len, UC_PROT_ALL));
1953+
1954+
char code[] = "\x0F\x01\xF9"; // RDTSCP
1955+
OK(uc_mem_write(uc, code_start, code, sizeof code - 1));
1956+
1957+
uc_hook hook;
1958+
OK(uc_hook_add(uc, &hook, UC_HOOK_INSN, test_x86_hook_insn_rdtscp_cb, NULL,
1959+
1, 0, UC_X86_INS_RDTSCP));
1960+
1961+
OK(uc_emu_start(uc, code_start, code_start + sizeof code - 1, 0, 0));
1962+
1963+
OK(uc_hook_del(uc, hook));
1964+
1965+
uint64_t h = 0;
1966+
OK(uc_reg_read(uc, UC_X86_REG_RDX, &h));
1967+
TEST_CHECK(h == 0x0000000001234567);
1968+
1969+
uint64_t l = 0;
1970+
OK(uc_reg_read(uc, UC_X86_REG_RAX, &l));
1971+
TEST_CHECK(l == 0x0000000089ABCDEF);
1972+
1973+
uint64_t i = 0;
1974+
OK(uc_reg_read(uc, UC_X86_REG_RCX, &i));
1975+
TEST_CHECK(i == 0x00000000DEADBEEF);
1976+
1977+
OK(uc_close(uc));
1978+
}
1979+
18941980
TEST_LIST = {
18951981
{"test_x86_in", test_x86_in},
18961982
{"test_x86_out", test_x86_out},
@@ -1947,4 +2033,6 @@ TEST_LIST = {
19472033
{"test_bswap_x64", test_bswap_ax},
19482034
{"test_rex_x64", test_rex_x64},
19492035
{"test_x86_ro_segfault", test_x86_ro_segfault},
2036+
{"test_x86_hook_insn_rdtsc", test_x86_hook_insn_rdtsc},
2037+
{"test_x86_hook_insn_rdtscp", test_x86_hook_insn_rdtscp},
19502038
{NULL, NULL}};

0 commit comments

Comments
 (0)