lifter: model XGETBV deterministically (#107)

* lifter: model XGETBV deterministically

Add XGETBV opcode support and model selector 0 as a deterministic XCR0 value (0x7: x87+SSE+AVX enabled), with zero returned for other selectors. This follows the existing CPUID deterministic-model approach for static lifting/deobfuscation.

Verification:

- build_iced lifter rewrite_microtests

- rewrite_microtests.exe xgetbv_returns_deterministic_xcr0 int29_fastfail_lowered_to_noreturn_call solve_path_widens_mapped_rva_target normalize_runtime_target_widens_mapped_rva_target

- python test.py quick

- python test.py vmp

* rewrite: seed deterministic XGETBV handler

The XGETBV semantics patch is deterministic by design, so the full-handler oracle pipeline must not use Unicorn's host-specific result. Add a manual handler seed entry for xgetbv bytes and computed expected outputs, then regenerate the enriched seed and oracle vectors to match the lifter model (selector 0 -> EAX=0x7, EDX=0).

Verification:

- scripts\rewrite\run_all_handlers.cmd

- python test.py quick

- python test.py vmp

---------

Co-authored-by: yusufcanislek <yusuf.canislek@meetdandy.com>
This commit is contained in:
naci
2026-04-20 19:29:01 +03:00
committed by GitHub
parent 6d11b8a746
commit b2699fb3a8
8 changed files with 164 additions and 0 deletions
+19
View File
@@ -535,6 +535,25 @@ MERGEN_LIFTER_DEFINITION_TEMPLATES(void)::lift_cpuid() {
ConstantInt::get(Type::getInt32Ty(context), 0xBFEBFBFF));
}
MERGEN_LIFTER_DEFINITION_TEMPLATES(void)::lift_xgetbv() {
LLVMContext& context = builder->getContext();
auto* selector = createZExtOrTruncFolder(
GetRegisterValue(Register::ECX), Type::getInt32Ty(context));
// Deterministic XCR0 model for static lifting. The startup path only queries
// selector 0 and checks bits 1:2 (SSE/AVX state enabled). Return XCR0=0x7
// so x87 (bit 0, architecturally always enabled) is also modeled correctly.
auto* selectorIsZero = createICMPFolder(
CmpInst::ICMP_EQ, selector, ConstantInt::get(Type::getInt32Ty(context), 0));
auto* eaxValue = createSelectFolder(
selectorIsZero, ConstantInt::get(Type::getInt32Ty(context), 0x7),
ConstantInt::get(Type::getInt32Ty(context), 0), "xgetbv.eax");
auto* edxValue = ConstantInt::get(Type::getInt32Ty(context), 0);
SetRegisterValue(Register::EAX, eaxValue);
SetRegisterValue(Register::EDX, edxValue);
}
uint64_t alternative_pext(uint64_t source, uint64_t mask) {
uint64_t result = 0;
int bit_position = 0;
+1
View File
@@ -97,6 +97,7 @@ OPCODE(test, TEST)
OPCODE(cmp, CMP)
OPCODE(rdtsc, RDTSC)
OPCODE(cpuid, CPUID)
OPCODE(xgetbv, XGETBV)
OPCODE(pext, PEXT)
//
OPCODE(setnz, SETNZ)
+24
View File
@@ -429,6 +429,28 @@ private:
return true;
}
bool runXgetbvReturnsDeterministicXcr0(std::string& details) {
LifterUnderTest lifter;
lifter.SetRegisterValue(RegisterUnderTest::RCX,
makeI64(lifter.builder->getContext(), 0));
static constexpr uint8_t kXgetbv[] = {0x0F, 0x01, 0xD0};
lifter.liftBytes(kXgetbv, sizeof(kXgetbv));
auto eaxAfter = readConstantAPInt(
lifter.GetRegisterValue(RegisterUnderTest::EAX));
auto edxAfter = readConstantAPInt(
lifter.GetRegisterValue(RegisterUnderTest::EDX));
if (!eaxAfter.has_value() || eaxAfter->getZExtValue() != 0x7) {
details = " xgetbv should set EAX to modeled XCR0 low bits (0x7)\n";
return false;
}
if (!edxAfter.has_value() || edxAfter->getZExtValue() != 0) {
details = " xgetbv should clear EDX for the modeled XCR0 high bits\n";
return false;
}
return true;
}
bool runLoopGeneralizationConditionalBranchAllowed(std::string& details) {
LifterUnderTest lifter;
lifter.currentPathSolveContext =
@@ -1248,6 +1270,8 @@ private:
&InstructionTester::runLoopGeneralizationIndirectJumpBlockedWhenUnresolved);
runCustom("int29_fastfail_lowered_to_noreturn_call",
&InstructionTester::runInt29FastfailLoweredToNoReturnCall);
runCustom("xgetbv_returns_deterministic_xcr0",
&InstructionTester::runXgetbvReturnsDeterministicXcr0);
runCustom("loop_generalization_indirect_jump_allowed_when_resolved",
&InstructionTester::runLoopGeneralizationIndirectJumpAllowedWhenResolved);
runCustom("loop_generalization_ret_blocked",
@@ -6106,6 +6106,42 @@
"flags": {}
}
}
},
{
"name": "smoke_xgetbv_xgetbv",
"handler": "xgetbv",
"oracle_mode": "computed",
"instruction_bytes": [
15,
1,
208
],
"initial": {
"registers": {
"RAX": "0x1122334455667788",
"RBX": "0x8877665544332211",
"RCX": "0x0",
"RDX": "0x2"
},
"flags": {
"FLAG_CF": 0,
"FLAG_PF": 0,
"FLAG_AF": 0,
"FLAG_ZF": 0,
"FLAG_SF": 0,
"FLAG_OF": 0,
"FLAG_DF": 0,
"FLAG_IF": 1
}
},
"expected": {
"registers": {
"RAX": "0x7",
"RDX": "0x0"
},
"flags": {}
},
"oracle_observations": {}
}
]
}
@@ -162,6 +162,14 @@ MANUAL_HANDLER_CASES = {
"flags": {},
},
},
"xgetbv": {
"mnemonic": "xgetbv",
"instruction_bytes": [0x0F, 0x01, 0xD0],
"initial": {
"registers": {"RCX": "0x0"},
"flags": {},
},
},
# ---- Control flow ----
"call": {
"mnemonic": "call",
+7
View File
@@ -184,6 +184,12 @@ def _compute_stosx(initial):
def _compute_cli(_initial):
return {"registers": {}, "flags": {"FLAG_IF": 0}}
def _compute_xgetbv(initial):
rcx = _get_initial_register(initial, "RCX", 0) & 0xFFFFFFFF
eax = 0x7 if rcx == 0 else 0
return {"registers": {"RAX": hex(eax), "RDX": hex(0)}, "flags": {}}
COMPUTED_HANDLERS = {
"push": _compute_push,
@@ -197,6 +203,7 @@ COMPUTED_HANDLERS = {
"movs_x": _compute_movs_x,
"stosx": _compute_stosx,
"cli": _compute_cli,
"xgetbv": _compute_xgetbv,
}
# Non-deterministic system instructions — truly untestable
@@ -4656,6 +4656,39 @@
},
"oracle": "none",
"source": "capstone-auto-discovery"
},
{
"name": "smoke_xgetbv_xgetbv",
"handler": "xgetbv",
"instruction_bytes": [
15,
1,
208
],
"initial": {
"registers": {
"RAX": "0x1122334455667788",
"RBX": "0x8877665544332211",
"RCX": "0x0",
"RDX": "0x2"
},
"flags": {
"FLAG_CF": 0,
"FLAG_PF": 0,
"FLAG_AF": 0,
"FLAG_ZF": 0,
"FLAG_SF": 0,
"FLAG_OF": 0,
"FLAG_DF": 0,
"FLAG_IF": 1
}
},
"expected": {
"registers": {},
"flags": {}
},
"oracle": "none",
"source": "capstone-auto-discovery"
}
]
}
@@ -5125,6 +5125,42 @@
},
"oracle": "unicorn",
"source": "capstone-auto-discovery"
},
{
"name": "smoke_xgetbv_xgetbv",
"handler": "xgetbv",
"instruction_bytes": [
15,
1,
208
],
"initial": {
"registers": {
"RAX": "0x1122334455667788",
"RBX": "0x8877665544332211",
"RCX": "0x0",
"RDX": "0x2"
},
"flags": {
"FLAG_CF": 0,
"FLAG_PF": 0,
"FLAG_AF": 0,
"FLAG_ZF": 0,
"FLAG_SF": 0,
"FLAG_OF": 0,
"FLAG_DF": 0,
"FLAG_IF": 1
}
},
"expected": {
"registers": {
"RAX": "0x7",
"RDX": "0x0"
},
"flags": {}
},
"oracle": "computed",
"source": "capstone-auto-discovery"
}
]
}