Patch CPUID

This commit is contained in:
offtkp 2024-09-16 23:10:19 +03:00
parent 28ec489dbe
commit 7d6a01cc29
4 changed files with 79 additions and 7 deletions

View File

@ -798,4 +798,50 @@ void PrePatchInstructions(u64 segment_addr, u64 segment_size) {
#endif
}
// We patch CPUID to handle it specially and simulate an AMD environment.
// Because CPUID is at minimum 2 bytes, a trampoline jump is not possible.
// Instead, we replace the instruction with an illegal instruction and handle it in the illegal instruction handler.
void PatchCPUID(u64 segment_addr, u64 segment_size) {
u8* code = reinterpret_cast<u8*>(segment_addr);
u8* end = code + segment_size;
bool previous_instruction_ok = false;
while (code < end) {
ZydisDecodedInstruction instruction;
ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT];
const auto status = ZydisDecoderDecodeFull(&instr_decoder, code, end - code, &instruction, operands);
if (!ZYAN_SUCCESS(status)) {
code++;
previous_instruction_ok = false;
continue;
}
if (instruction.mnemonic == ZYDIS_MNEMONIC_CPUID) {
if (!previous_instruction_ok) {
// It should be highly unlikely that the CPUID instruction is not preceded by a valid instruction.
// Instead of patching it we're going to skip it and generate a warning, because we could be in some
// data section that must not be changed.
LOG_ERROR(Core, "CPUID instruction is not preceded by a valid instruction at: {} while patching", fmt::ptr(code));
code++;
continue;
}
LOG_CRITICAL(Core, "FOUND CPUID INSTRUCTION AT: {}", fmt::ptr(code));
// Replace any potential prefixes and the 0F byte with single byte NOPs
u8 instruction_length = instruction.length;
for (u8 i = 0; i < instruction_length - 1; i++) {
code[i] = 0x90;
}
// Replace the CPUID instruction with an illegal instruction
code[instruction_length - 1] = 0x37; // 0x37 is AAA, illegal in x86-64
}
code += instruction.length;
previous_instruction_ok = true;
}
}
} // namespace Core

View File

@ -18,4 +18,7 @@ void RegisterPatchModule(void* module_ptr, u64 module_size, void* trampoline_are
/// Applies CPU patches that need to be done before beginning executions.
void PrePatchInstructions(u64 segment_addr, u64 segment_size);
// Patches CPUID instructions with an illegal instruction to handle them specially in the signal handler
void PatchCPUID(u64 segment_addr, u64 segment_size);
} // namespace Core

View File

@ -137,6 +137,7 @@ void Module::LoadModuleToMemory(u32& max_tls_index) {
#ifdef ARCH_X86_64
if (elf_pheader[i].p_flags & PF_EXEC) {
PrePatchInstructions(segment_addr, segment_file_size);
PatchCPUID(segment_addr, segment_file_size);
}
#endif
break;

View File

@ -7,8 +7,10 @@
#ifdef _WIN32
#include <windows.h>
#include <intrin.h>
#else
#include <csignal>
#include <cpuid.h>
#ifdef ARCH_X86_64
#include <Zydis/Decoder.h>
#include <Zydis/Formatter.h>
@ -31,9 +33,16 @@ static LONG WINAPI SignalHandler(EXCEPTION_POINTERS* pExp) noexcept {
code_address, reinterpret_cast<void*>(pExp->ExceptionRecord->ExceptionInformation[1]),
pExp->ExceptionRecord->ExceptionInformation[0] == 1);
break;
case EXCEPTION_ILLEGAL_INSTRUCTION:
handled = signals->DispatchIllegalInstruction(code_address);
case EXCEPTION_ILLEGAL_INSTRUCTION: {
u8 opcode = *reinterpret_cast<u8*>(code_address);
if (opcode == 0x37) { // 0x37 is AAA instruction, we use that to patch CPUID
handled = EXCEPTION_CONTINUE_EXECUTION;
pExp->ContextRecord->Rip += 1; // skip the illegal instruction
} else {
handled = signals->DispatchIllegalInstruction(code_address);
}
break;
}
default:
break;
}
@ -87,7 +96,7 @@ static std::string DisassembleInstruction(void* code_address) {
}
static void SignalHandler(int sig, siginfo_t* info, void* raw_context) {
const auto* ctx = static_cast<ucontext_t*>(raw_context);
auto* ctx = static_cast<ucontext_t*>(raw_context);
const auto* signals = Signals::Instance();
auto* code_address = CODE_ADDRESS(ctx);
@ -102,12 +111,25 @@ static void SignalHandler(int sig, siginfo_t* info, void* raw_context) {
fmt::ptr(info->si_addr));
}
break;
case SIGILL:
if (!signals->DispatchIllegalInstruction(code_address)) {
UNREACHABLE_MSG("Unhandled illegal instruction at code address {}: {}",
fmt::ptr(code_address), DisassembleInstruction(code_address));
case SIGILL: {
u8 opcode = *reinterpret_cast<u8*>(code_address);
if (opcode == 0x37) { // 0x37 is AAA instruction, we use that to patch CPUID
u64 results[4];
u64 eax = ctx->uc_mcontext.gregs[REG_RAX];
u64 ecx = ctx->uc_mcontext.gregs[REG_RCX];
LOG_WARNING(Core, "CPUID: eax=%lx ecx=%lx\n", eax, ecx);
__cpuid_count(eax, ecx, results[0], results[1], results[2], results[3]);
LOG_WARNING(Core, "RESULTS: %lx %lx %lx %lx\n", results[0], results[1], results[2], results[3]);
ctx->uc_mcontext.gregs[REG_RIP] += 1; // skip the illegal instruction
break;
} else {
if (!signals->DispatchIllegalInstruction(code_address)) {
UNREACHABLE_MSG("Unhandled illegal instruction at code address {}: {}",
fmt::ptr(code_address), DisassembleInstruction(code_address));
}
}
break;
}
default:
break;
}