Files
Mergen/lifter/semantics/Semantics_Bitwise.ipp
naci 194f43b56d lifter: guard pop rsp against out-of-range concrete values (#194)
A concrete RSP produced by `pop rsp` that falls outside the tracked
pseudo-stack range crashes the lifter on the next [rsp] memory op, via
an unmapped dereference inside GetMemoryValue/solveLoad. This shape
appears in VM stack-switch gadgets (example: the Themida-virt handler
at 0x14017facc..fae3) and the crash kills deep-exploration sweeps.

When lift_pop detects the destination operand is RSP and Rvalue is a
ConstantInt outside `isTrackedStackAddress`, emit a structured warning
and an unreachable terminator for the block instead of writing the bad
RSP and letting the next memory op take us down.

Symbolic Rvalue is unchanged - the existing symbolic-load path already
handles it. ConstantInt within the tracked stack range is unchanged -
legitimate `pop rsp` on a real stack pointer stays supported.

Verified on example2-virt.bin @ 0x140001000:
  default (no chain, T=16 IndirectJump):  359 blocks, 0 warn, 0 err
  chain + T=32:   before: SEGV at block 755 / PC 0x14017fae1
                  after:  755 blocks, 1/4 imports, 2 warn, 0 err, exit 0
  chain + NO_LOOP_GEN: 2972 blocks, 1/4, exit 0 (no more crash)

Baseline rewrite regression + determinism checks remain green. Default
themida lift unchanged (359 blocks, 1/4 imports). The guard fires only
when exploration actually reaches the gadget, which at current defaults
it does not.

Co-authored-by: Claude <claude@anthropic.com>
2026-04-24 20:12:36 +03:00

514 lines
17 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Semantics_Bitwise.ipp — Bitwise ops, rotate, inc/dec, stack push/pop
MERGEN_LIFTER_DEFINITION_TEMPLATES(void)::lift_xor() {
/* auto dest = operands[0];
auto src = operands[1]; */
auto Lvalue = GetIndexValue(0);
auto Rvalue = GetIndexValue(1);
Rvalue = createSExtFolder(Rvalue, Lvalue->getType());
auto result = createXorFolder(
Lvalue, Rvalue, "realxor-" + std::to_string(current_address) + "-");
printvalue(Lvalue) printvalue(Rvalue) printvalue(result);
// auto pf = computeParityFlag(result);
// The OF and CF flags are cleared; the SF, ZF, and PF flags are set
// according to the result. The state of the AF flag is undefined.
setFlag(FLAG_SF, [this, result]() { return computeSignFlag(result); });
setFlag(FLAG_ZF, [this, result]() { return computeZeroFlag(result); });
setFlag(FLAG_PF, [this, result]() { return computeParityFlag(result); });
setFlag(FLAG_OF, [this]() {
return ConstantInt::getSigned(Type::getInt1Ty(builder->getContext()), 0);
});
setFlag(FLAG_CF, [this]() {
return ConstantInt::getSigned(Type::getInt1Ty(builder->getContext()), 0);
});
SetIndexValue(0, result);
}
MERGEN_LIFTER_DEFINITION_TEMPLATES(void)::lift_or() {
// LLVMContext& context = builder->getContext();
/* auto dest = operands[0];
auto src = operands[1]; */
auto Lvalue = GetIndexValue(0);
auto Rvalue = GetIndexValue(1);
Rvalue = createSExtFolder(Rvalue, Lvalue->getType());
auto result = createOrFolder(
Lvalue, Rvalue, "realor-" + std::to_string(current_address) + "-");
printvalue(Lvalue);
printvalue(Rvalue);
printvalue(result);
// auto pf = computeParityFlag(result);
// The OF and CF flags are cleared; the SF, ZF, and PF flags are set
// according to the result. The state of the AF flag is undefined.
setFlag(FLAG_SF, [this, result]() { return computeSignFlag(result); });
setFlag(FLAG_ZF, [this, result]() { return computeZeroFlag(result); });
setFlag(FLAG_PF, [this, result]() { return computeParityFlag(result); });
setFlag(FLAG_OF, [this]() {
return ConstantInt::getSigned(Type::getInt1Ty(builder->getContext()), 0);
});
setFlag(FLAG_CF, [this]() {
return ConstantInt::getSigned(Type::getInt1Ty(builder->getContext()), 0);
});
SetIndexValue(0, result);
}
MERGEN_LIFTER_DEFINITION_TEMPLATES(void)::lift_and() {
// LLVMContext& context = builder->getContext();
/* auto dest = operands[0];
auto src = operands[1]; */
auto Lvalue = GetIndexValue(0);
auto Rvalue = GetIndexValue(1);
Rvalue = createSExtFolder(Rvalue, Lvalue->getType());
auto result = createAndFolder(
Lvalue, Rvalue, "realand-" + std::to_string(current_address) + "-");
// auto pf = computeParityFlag(result);
// The OF and CF flags are cleared; the SF, ZF, and PF flags are set
// according to the result. The state of the AF flag is undefined.
setFlag(FLAG_SF, [this, result]() { return computeSignFlag(result); });
setFlag(FLAG_ZF, [this, result]() { return computeZeroFlag(result); });
setFlag(FLAG_PF, [this, result]() { return computeParityFlag(result); });
setFlag(FLAG_OF, [this]() {
return ConstantInt::getSigned(Type::getInt1Ty(builder->getContext()), 0);
});
setFlag(FLAG_CF, [this]() {
return ConstantInt::getSigned(Type::getInt1Ty(builder->getContext()), 0);
});
printvalue(Lvalue) printvalue(Rvalue) printvalue(result);
SetIndexValue(0, result);
}
MERGEN_LIFTER_DEFINITION_TEMPLATES(void)::lift_andn() {
// ANDN (VEX): dest = ~src1 & src2, so operands map to [dest, src1, src2].
auto src1 = GetIndexValue(1);
auto src2 = GetIndexValue(2);
auto result = createAndFolder(
createNotFolder(src1), src2,
"realand-" + std::to_string(current_address) + "-");
setFlag(FLAG_SF, [this, result]() { return computeSignFlag(result); });
setFlag(FLAG_ZF, [this, result]() { return computeZeroFlag(result); });
setFlag(FLAG_PF, [this, result]() { return computeParityFlag(result); });
setFlag(FLAG_OF, [this]() {
return ConstantInt::getSigned(Type::getInt1Ty(builder->getContext()), 0);
});
setFlag(FLAG_CF, [this]() {
return ConstantInt::getSigned(Type::getInt1Ty(builder->getContext()), 0);
});
printvalue(src1) printvalue(src2) printvalue(result);
SetIndexValue(0, result);
}
/*
tempCOUNT := (COUNT & COUNTMASK) MOD SIZE
WHILE (tempCOUNT ≠ 0)
DO
tempCF := MSB(DEST);
DEST := (DEST 2) + tempCF;
tempCOUNT := tempCOUNT 1;
OD;
ELIHW;
IF (COUNT & COUNTMASK) ≠ 0
THEN CF := LSB(DEST);
FI;
IF (COUNT & COUNTMASK) = 1
THEN OF := MSB(DEST) XOR CF;
ELSE OF is undefined;
FI
*/
MERGEN_LIFTER_DEFINITION_TEMPLATES(void)::lift_rol() {
LLVMContext& context = builder->getContext();
/* auto dest = operands[0];
auto src = operands[1]; */
auto destsize = GetTypeSize(instruction.types[0]);
auto Lvalue = GetIndexValue(0);
auto Rvalue = GetIndexValue(1);
Rvalue = createZExtFolder(Rvalue, Lvalue->getType());
auto bitWidth = ConstantInt::get(Lvalue->getType(), destsize);
auto countmask =
ConstantInt::get(Lvalue->getType(), destsize == 64 ? 0x3f : 0x1f);
auto one = ConstantInt::get(Lvalue->getType(), 1);
auto zero = ConstantInt::get(Lvalue->getType(), 0);
auto MSBpos = ConstantInt::get(Lvalue->getType(), destsize - 1);
Rvalue = createURemFolder(createAndFolder(Rvalue, countmask, "maskRvalue"),
bitWidth);
Value* shiftedLeft = createShlFolder(Lvalue, Rvalue);
Value* shiftedRight =
createLShrFolder(Lvalue, createSubFolder(bitWidth, Rvalue), "rol");
Value* result = createOrFolder(shiftedLeft, shiftedRight);
Value* cf = createZExtOrTruncFolder(shiftedRight, Type::getInt1Ty(context));
Value* isZeroBitRotation = createICMPFolder(CmpInst::ICMP_EQ, Rvalue, zero);
Value* oldcf = getFlag(FLAG_CF); // undefined
cf = createSelectFolder(isZeroBitRotation, oldcf, cf);
result = createSelectFolder(isZeroBitRotation, Lvalue, result);
// of = cf ^ MSB
Value* newMSB = createLShrFolder(result, MSBpos, "rolmsb");
auto of1 = createZExtOrTruncFolder(newMSB, Type::getInt1Ty(context));
Value* of = createXorFolder(cf, of1);
// crash?
// Use Select to conditionally update OF based on whether the shift
// amount is 1
Value* isOneBitRotation = createICMPFolder(CmpInst::ICMP_EQ, Rvalue, one);
Value* ofCurrent = getFlag(FLAG_OF);
of = createSelectFolder(isOneBitRotation, of, ofCurrent);
setFlag(FLAG_CF, cf);
setFlag(FLAG_OF, of);
printvalue(Lvalue) printvalue(Rvalue) printvalue(result);
SetIndexValue(0, result);
}
/*
tempCOUNT := (COUNT & COUNTMASK) MOD SIZE
WHILE (tempCOUNT ≠ 0)
DO
tempCF := LSB(SRC);
DEST := (DEST / 2) + (tempCF 2SIZE);
tempCOUNT := tempCOUNT 1;
OD;
ELIHW;
IF (COUNT & COUNTMASK) ≠ 0
THEN CF := MSB(DEST);
FI;
IF (COUNT & COUNTMASK) = 1
THEN OF := MSB(DEST) XOR MSB 1(DEST);
ELSE OF is undefined;
FI
*/
MERGEN_LIFTER_DEFINITION_TEMPLATES(void)::lift_ror() {
LLVMContext& context = builder->getContext();
/*
auto dest = operands[0];
auto src = operands[1]; */
auto destsize = GetTypeSize(instruction.types[0]);
auto Lvalue = GetIndexValue(0);
auto Rvalue = GetIndexValue(1);
Rvalue = createZExtFolder(Rvalue, Lvalue->getType());
auto bitWidth = ConstantInt::get(Lvalue->getType(), destsize);
auto countmask =
ConstantInt::get(Lvalue->getType(), destsize == 64 ? 0x3f : 0x1f);
auto one = ConstantInt::get(Lvalue->getType(), 1);
auto zero = ConstantInt::get(Lvalue->getType(), 0);
auto MSBpos = ConstantInt::get(Lvalue->getType(), destsize - 1);
auto secondMSBpos = ConstantInt::get(Lvalue->getType(), destsize - 2);
printvalue(Rvalue);
Rvalue = createURemFolder(createAndFolder(Rvalue, countmask, "maskRvalue"),
bitWidth);
Value* rightshifted = createLShrFolder(Lvalue, Rvalue);
Value* leftshifted =
createShlFolder(Lvalue, createSubFolder(bitWidth, Rvalue));
Value* result =
createOrFolder(rightshifted, leftshifted,
"ror-" + std::to_string(current_address) + "-");
Value* msb = createLShrFolder(result, MSBpos);
Value* cf = createZExtOrTruncFolder(msb, Type::getInt1Ty(context), "ror-cf");
Value* secondMsb = createLShrFolder(result, secondMSBpos, "ror2ndmsb");
auto ofDefined =
createZExtOrTruncFolder(createXorFolder(msb, secondMsb), cf->getType());
auto isOneBitRotation = createICMPFolder(CmpInst::ICMP_EQ, Rvalue, one);
auto isZeroBitRotation = createICMPFolder(CmpInst::ICMP_EQ, Rvalue, zero);
Value* ofCurrent = getFlag(FLAG_OF);
Value* of =
createSelectFolder(isOneBitRotation, ofDefined, ofCurrent, "ror-of");
cf = createSelectFolder(isZeroBitRotation, getFlag(FLAG_CF), cf);
setFlag(FLAG_CF, cf);
setFlag(FLAG_OF, of);
result = createSelectFolder(isZeroBitRotation, Lvalue, result, "ror-result");
printvalue(Lvalue) printvalue(Rvalue) printvalue(result);
SetIndexValue(0, result);
}
MERGEN_LIFTER_DEFINITION_TEMPLATES(void)::lift_inc() {
// auto operand = operands[0];
Value* Lvalue = GetIndexValue(0);
Value* one = ConstantInt::get(Lvalue->getType(), 1, true);
Value* result = createAddFolder(
Lvalue, one, "inc-" + std::to_string(current_address) + "-");
// Value* of = computeOverflowFlagAdd(Lvalue, one, result);
// The CF flag is not affected. The OF, SF, ZF, AF, and PF flags are set
// according to the result.
// treat it as add r, 1 for flags
printvalue(Lvalue) printvalue(result);
// Value* sf = computeSignFlag(result);
// Value* zf = computeZeroFlag(result);
// Value* pf = computeParityFlag(result);
setFlag(FLAG_OF, [this, result, one, Lvalue]() {
return computeOverflowFlagAdd(Lvalue, one, result);
});
setFlag(FLAG_SF, [this, result]() { return computeSignFlag(result); });
setFlag(FLAG_ZF, [this, result]() { return computeZeroFlag(result); });
setFlag(FLAG_PF, [this, result]() { return computeParityFlag(result); });
setFlag(FLAG_AF, [this, Lvalue, one]() {
auto lowerNibbleMask = ConstantInt::get(Lvalue->getType(), 0xF);
auto destLowerNibble = createAndFolder(Lvalue, lowerNibbleMask, "adcdst");
auto srcLowerNibble = one;
auto sumLowerNibble = createAddFolder(destLowerNibble, srcLowerNibble);
return createICMPFolder(CmpInst::ICMP_UGT, sumLowerNibble, lowerNibbleMask);
});
SetIndexValue(0, result);
}
MERGEN_LIFTER_DEFINITION_TEMPLATES(void)::lift_dec() {
// auto operand = operands[0];
Value* Lvalue = GetIndexValue(0);
Value* one = ConstantInt::get(Lvalue->getType(), 1, true);
Value* result = createSubFolder(
Lvalue, one, "dec-" + std::to_string(current_address) + "-");
Value* of = computeOverflowFlagSub(Lvalue, one, result);
// The CF flag is not affected. The OF, SF, ZF, AF, and PF flags are set
// according to the result.
// treat it as sub r, 1 for flags
printvalue(Lvalue) printvalue(result);
Value* sf = computeSignFlag(result);
Value* zf = computeZeroFlag(result);
Value* pf = computeParityFlag(result);
printvalue(sf);
setFlag(FLAG_OF, of);
setFlag(FLAG_SF, sf);
setFlag(FLAG_ZF, zf);
setFlag(FLAG_PF, pf);
auto lowerNibbleMask = ConstantInt::get(Lvalue->getType(), 0xF);
auto RvalueLowerNibble =
createAndFolder(Lvalue, lowerNibbleMask, "lvalLowerNibble");
auto op2LowerNibble = one;
auto af = createICMPFolder(CmpInst::ICMP_ULT, RvalueLowerNibble,
op2LowerNibble, "sub_af");
setFlag(FLAG_AF, af);
SetIndexValue(0, result);
}
MERGEN_LIFTER_DEFINITION_TEMPLATES(void)::lift_push() {
LLVMContext& context = builder->getContext();
/* auto src = operands[0]; // value that we are pushing
auto dest = operands[2];
auto rsp = operands[1]; */
auto Rvalue = GetIndexValue(0);
auto RspValue = GetRegisterValue(Register::RSP);
auto destsize = instruction.stack_growth;
printvalue2(destsize);
auto val = ConstantInt::getSigned(Type::getInt64Ty(context),
destsize); //
auto result = createSubFolder(
RspValue, val, "pushing_newrsp-" + std::to_string(current_address) + "-");
printvalue(Rvalue);
printvalue(RspValue);
printvalue(result);
SetRegisterValue(Register::RSP, result);
// SetIndexValue(rsp, result, std::to_string(current_address));
// sub rsp 8 first,
// sign extend
switch (instruction.types[0]) {
// case OperandType::Immediate64:
case OperandType::Immediate8:
case OperandType::Immediate16:
case OperandType::Immediate32: {
Rvalue = createSExtFolder(Rvalue,
builder->getIntNTy(instruction.stack_growth * 8));
break;
}
default:
break;
}
SetMemoryValue(getSPaddress(), Rvalue);
// SetIndexValue(dest, Rvalue, std::to_string(current_address));
// then mov rsp, val
}
MERGEN_LIFTER_DEFINITION_TEMPLATES(void)::lift_pushfq() {
LLVMContext& context = builder->getContext();
/* auto src = operands[2]; // value that we are pushing rflags
auto dest = operands[1]; // [rsp]
auto rsp = operands[0]; // rsp */
auto Rvalue = GetRFLAGSValue();
// auto Rvalue = GetRFLAGS(builder);
auto RspValue = GetRegisterValue(Register::RSP);
auto srcsize = 64; // based on bitness ---
auto val = ConstantInt::get(Type::getInt64Ty(context), srcsize / 8);
auto result = createSubFolder(RspValue, val);
SetRegisterValue(Register::RSP, result);
// SetIndexValue(rsp, result, std::to_string(current_address));
// sub rsp 8 first,
// pushFlags( dest, Rvalue,
// std::to_string(current_address));;
SetMemoryValue(getSPaddress(), Rvalue);
// SetIndexValue(dest, Rvalue, std::to_string(current_address));
// then mov rsp, val
}
MERGEN_LIFTER_DEFINITION_TEMPLATES(void)::lift_pop() {
LLVMContext& context = builder->getContext();
/* auto dest = operands[0]; // value that we are pushing
auto src = operands[2];
auto rsp = operands[1]; */
auto destsize = instruction.stack_growth;
auto Rvalue = GetMemoryValue(getSPaddress(), destsize * 8); // [rsp]
auto RspValue = GetRegisterValue(Register::RSP);
auto val = ConstantInt::getSigned(Type::getInt64Ty(context), destsize);
auto result = createAddFolder(RspValue, val,
"popping_new_rsp-" +
std::to_string(current_address) + "-");
printvalue(Rvalue) printvalue(RspValue) printvalue(result);
SetRegisterValue(Register::RSP, result); // then add rsp 8
// Guard: `pop rsp` (destination operand IS RSP) with a concrete popped
// value outside the tracked pseudo-stack range is a VM stack-switch
// gadget whose subsequent [rsp] loads dereference into unmapped
// memory and crash the lifter. When we can see this statically, bail
// the block with a structured warning and an unreachable terminator
// instead of letting the next memory op take us down. Symbolic Rvalue
// is already handled by the existing symbolic-load path.
if (instruction.types[0] >= OperandType::Register8 &&
instruction.types[0] <= OperandType::Register64 &&
getBiggestEncoding(instruction.regs[0]) == Register::RSP) {
if (auto* rvConst = llvm::dyn_cast<llvm::ConstantInt>(Rvalue)) {
uint64_t rv = rvConst->getZExtValue();
if (!isTrackedStackAddress(rv)) {
diagnostics.warning(
DiagCode::UnresolvedIndirectJump,
current_address - instruction.length,
"pop rsp loaded an out-of-range concrete value; bailing to"
" avoid a downstream unmapped-memory crash");
builder->CreateUnreachable();
run = 0;
finished = 1;
return;
}
}
}
SetIndexValue(0, Rvalue); // op
// mov val, rsp first
/* ???
Rvalue =
createZExtFolder(Rvalue, builder->getIntNTy(instruction.stack_growth));
*/
}
MERGEN_LIFTER_DEFINITION_TEMPLATES(void)::lift_leave() {
const auto stackRegisterSize = file.getMode() == arch_mode::X64 ? 64 : 32;
const auto stackRegister = getRegOfSize(Register::RSP, stackRegisterSize);
const auto frameRegister = getRegOfSize(Register::RBP, stackRegisterSize);
auto frameValue = GetRegisterValue(frameRegister);
SetRegisterValue(stackRegister, frameValue);
auto poppedFrame = popStack(stackRegisterSize / 8);
poppedFrame = createZExtOrTruncFolder(poppedFrame, frameValue->getType());
SetRegisterValue(frameRegister, poppedFrame);
}
MERGEN_LIFTER_DEFINITION_TEMPLATES(void)::lift_popfq() {
LLVMContext& context = builder->getContext();
/* auto dest = operands[2]; // value that we are pushing
auto src = operands[1]; // [rsp]
auto rsp = operands[0]; // rsp */
auto destsize = 64; // GetTypeSize(instruction.types[0]);
auto Rvalue = GetMemoryValue(getSPaddress(), destsize); // [rsp]
auto RspValue = GetRegisterValue(Register::RSP);
auto val = ConstantInt::getSigned(Type::getInt64Ty(context), destsize / 8);
auto result = createAddFolder(
RspValue, val, "popfq-" + std::to_string(current_address) + "-");
SetRFLAGSValue(Rvalue);
// mov val, rsp first
SetRegisterValue(Register::RSP, result); // then add rsp 8
// then add rsp 8
}