Skip to main content

OpaquePredicatePass

Struct OpaquePredicatePass 

Source
pub struct OpaquePredicatePass<T: Target>(PhantomData<T>);
Expand description

Opaque predicate detection and removal pass.

Detects conditional expressions that always evaluate to the same value (always true or always false) and simplifies branches, comparisons, and phi nodes accordingly. Handles self-comparison, identity operations, number-theoretic predicates, null checks, range-based predicates, and nested predicate chains.

Generic over Target; the inner PhantomData lets the same struct host all analysis methods without holding runtime state.

Tuple Fields§

§0: PhantomData<T>

Implementations§

Source§

impl<T: Target> OpaquePredicatePass<T>

Source

const MAX_PREDICATE_DEPTH: usize = 16

Maximum recursion depth for nested predicate analysis.

Each level corresponds to one SSA instruction defining a comparison result that feeds into another comparison. 16 levels handles deeply nested opaque predicates from advanced obfuscators (e.g., PureLogs multi-level chains) while preventing stack overflow on pathological inputs.

Source

pub fn new() -> Self

Creates a new opaque predicate pass.

Source

fn analyze_predicate_with_cache( op: &SsaOp<T>, cache: &DefinitionCache<T>, depth: usize, ) -> PredicateResult

Analyzes a predicate operation with full context, dispatching by SsaOp kind.

Pattern-matching cascade:

  1. Self-comparison (Ceq/Clt/Cgt where left == right): immediate result.
  2. Equality analysis (Ceq): delegates to analyze_equality for XOR==0, SUB==0, MUL*0==0, AND&0==0, number-theoretic, constant, null, and nested patterns.
  3. Less-than / greater-than (Clt/Cgt): delegates to range-based and constant analysis.
  4. Zero-producing ops (Xor/Sub with left==right): returns Unknown since the result only becomes meaningful when used in a comparison (handled at that level).
  5. Remainder / Multiplication / And: delegates to specialized analyzers.

Supports recursion up to MAX_PREDICATE_DEPTH for nested predicates (e.g., ceq(ceq(x, x), 1)).

§Arguments
  • op - The SSA operation to analyze (typically a comparison or arithmetic op).
  • cache - Pre-built definition cache for efficient variable resolution.
  • depth - Current recursion depth (0 at the top level).
§Returns

PredicateResult::AlwaysTrue or AlwaysFalse if the predicate can be statically determined, Unknown otherwise.

Source

fn analyze_equality( left: SsaVarId, right: SsaVarId, cache: &DefinitionCache<T>, depth: usize, ) -> PredicateResult

Analyzes an equality comparison (Ceq) for opaque predicate patterns.

Checks the following patterns (each with symmetric left/right variants):

  • (x ^ x) == 0 – XOR self-cancellation, always true.
  • (x - x) == 0 – subtraction self-cancellation, always true.
  • (x * 0) == 0 or (0 * x) == 0 – zero-producing multiplication, always true.
  • (x & 0) == 0 or (0 & x) == 0 – zero-producing AND, always true.
  • Number-theoretic: (x*(x+1)) % 2 == 0 and factored forms, always true.
  • Constant equality: both sides are constants with known values.
  • Null checks: non-null variable (from NewObj etc.) compared to null, always false.
  • Nested analysis fallback: if the left operand is itself a predicate (comparison), recursively analyzes it. If the result is known and compared to 1, returns that result; if compared to 0, returns the negation.
§Arguments
  • left - Left operand of the Ceq.
  • right - Right operand of the Ceq.
  • cache - Definition cache for resolving variable definitions.
  • depth - Current recursion depth for nested predicate analysis.
§Returns

PredicateResult::AlwaysTrue or AlwaysFalse if the equality can be statically determined, Unknown otherwise.

Source

fn analyze_less_than( left: SsaVarId, right: SsaVarId, unsigned: bool, cache: &DefinitionCache<T>, _depth: usize, ) -> PredicateResult

Analyzes a less-than comparison (Clt) for opaque predicate patterns.

Checks in order:

  1. Constant comparison: both operands are constants, evaluate directly (signed or unsigned).
  2. Range-based: if both operands have known ValueRanges, checks whether left.max < right.min (always true) or left.min >= right.max (always false).
  3. Left range vs. constant right: uses ValueRange::always_less_than.
  4. Unsigned bounds: x <.un 0 is always false (no unsigned value is less than zero).
  5. Non-negative check: if left is known non-negative (e.g., ArrayLength), then left < 0 is always false.
§Arguments
  • left - Left operand of the comparison.
  • right - Right operand of the comparison.
  • unsigned - Whether this is an unsigned comparison (clt.un).
  • cache - Definition cache for resolving variable definitions and ranges.
  • _depth - Unused (less-than analysis is non-recursive).
§Returns

PredicateResult::AlwaysTrue or AlwaysFalse if the less-than comparison can be statically determined, Unknown otherwise.

Source

fn analyze_greater_than( left: SsaVarId, right: SsaVarId, unsigned: bool, cache: &DefinitionCache<T>, _depth: usize, ) -> PredicateResult

Analyzes a greater-than comparison (Cgt) for opaque predicate patterns.

Checks in order:

  1. Constant comparison: both operands are constants, evaluate directly (signed or unsigned).
  2. Range-based: if both operands have known ValueRanges, checks whether left.min > right.max (always true) or left.max <= right.min (always false).
  3. Left range vs. constant right: uses ValueRange::always_greater_than.
  4. Unsigned bounds: 0 >.un x is always false (zero is never greater than any unsigned value).
  5. Non-negative vs. negative: if left is known non-negative and right is a negative constant, returns always true.
§Arguments
  • left - Left operand of the comparison.
  • right - Right operand of the comparison.
  • unsigned - Whether this is an unsigned comparison (cgt.un).
  • cache - Definition cache for resolving variable definitions and ranges.
  • _depth - Unused (greater-than analysis is non-recursive).
§Returns

PredicateResult::AlwaysTrue or AlwaysFalse if the greater-than comparison can be statically determined, Unknown otherwise.

Source

fn analyze_remainder( _left: SsaVarId, right: SsaVarId, cache: &DefinitionCache<T>, _depth: usize, ) -> PredicateResult

Analyzes a remainder operation (Rem) for x % 1 which always produces zero.

Returns Unknown rather than AlwaysTrue/AlwaysFalse because the zero result is only meaningful when subsequently compared (handled by the equality analysis).

§Arguments
  • _left - Left operand of the remainder (unused; any value mod 1 is zero).
  • right - Right operand of the remainder (checked for constant 1).
  • cache - Definition cache for resolving variable definitions.
  • _depth - Unused (remainder analysis is non-recursive).
§Returns

Always returns PredicateResult::Unknown because the zero result is only meaningful when used in a subsequent comparison.

Source

fn analyze_multiplication( left: SsaVarId, right: SsaVarId, cache: &DefinitionCache<T>, _depth: usize, ) -> PredicateResult

Analyzes a multiplication (Mul) for zero-producing patterns (x * 0 or 0 * x).

Returns Unknown because the zero result is only meaningful when subsequently compared (handled by the equality analysis at the comparison level).

§Arguments
  • left - Left operand of the multiplication.
  • right - Right operand of the multiplication.
  • cache - Definition cache for resolving variable definitions.
  • _depth - Unused (multiplication analysis is non-recursive).
§Returns

Always returns PredicateResult::Unknown because the zero result is only meaningful when used in a subsequent comparison.

Source

fn analyze_and( left: SsaVarId, right: SsaVarId, cache: &DefinitionCache<T>, _depth: usize, ) -> PredicateResult

Analyzes a bitwise AND (And) for zero-producing patterns (x & 0 or 0 & x).

Returns Unknown because the zero result is only meaningful when subsequently compared (handled by the equality analysis at the comparison level).

§Arguments
  • left - Left operand of the AND.
  • right - Right operand of the AND.
  • cache - Definition cache for resolving variable definitions.
  • _depth - Unused (AND analysis is non-recursive).
§Returns

Always returns PredicateResult::Unknown because the zero result is only meaningful when used in a subsequent comparison.

Source

fn is_zero_constant(op: &SsaOp<T>) -> bool

Checks if an operation produces a constant zero.

Source

fn is_one_constant(op: &SsaOp<T>) -> bool

Checks if an operation produces a constant one.

Source

fn is_null_constant(op: &SsaOp<T>) -> bool

Checks if an operation produces a null constant.

Source

fn is_minus_one_constant(op: &SsaOp<T>) -> bool

Checks if an operation produces a constant -1.

Source

fn is_zero_producing_mul( op: Option<&SsaOp<T>>, cache: &DefinitionCache<T>, ) -> bool

Returns true if the operation is a Mul where either operand is a constant zero.

§Arguments
  • op - The operation to check, or None if the variable has no definition.
  • cache - Definition cache for resolving the multiplication operands.
§Returns

true if op is a Mul with at least one constant-zero operand, false otherwise.

Source

fn is_zero_producing_and( op: Option<&SsaOp<T>>, cache: &DefinitionCache<T>, ) -> bool

Returns true if the operation is an And where either operand is a constant zero.

§Arguments
  • op - The operation to check, or None if the variable has no definition.
  • cache - Definition cache for resolving the AND operands.
§Returns

true if op is an And with at least one constant-zero operand, false otherwise.

Source

fn is_always_even_expression( op: Option<&SsaOp<T>>, cache: &DefinitionCache<T>, ) -> bool

Checks if an operation is an expression modulo 2 that always evaluates to 0.

Detects number-theoretic opaque predicates based on the mathematical property that the product of two consecutive integers is always even:

  • (x * (x + 1)) % 2 — direct consecutive product
  • (x * (x - 1)) % 2 — reversed consecutive product
  • (x * x - x) % 2 — factored form: x^2-x = x(x-1)
  • (x * x + x) % 2 — factored form: x^2+x = x(x+1)
§Arguments
  • op - The operation to check, or None if the variable has no definition.
  • cache - Definition cache for resolving operand definitions.
§Returns

true if the expression is a Rem by 2 whose dividend is always even, false otherwise.

Source

fn is_self_square( square_var: SsaVarId, other: SsaVarId, cache: &DefinitionCache<T>, ) -> bool

Checks if square_var is defined as other * other (i.e., other^2).

§Arguments
  • square_var - The variable suspected to be a square.
  • other - The variable that should appear as both operands of the multiplication.
  • cache - Definition cache for resolving the definition of square_var.
§Returns

true if square_var is defined as Mul { left: other, right: other }, false otherwise.

Source

fn is_consecutive_pair( a: SsaVarId, b: SsaVarId, cache: &DefinitionCache<T>, ) -> bool

Checks if two variables form a consecutive integer pair (n and n+1).

Performs three symmetric checks:

  • b = a + 1 (either operand order of the Add).
  • a = b + 1 (symmetric: a is the incremented one).
  • b = a - (-1) (subtraction of -1 is equivalent to adding 1).
§Arguments
  • a - First variable of the potential consecutive pair.
  • b - Second variable of the potential consecutive pair.
  • cache - Definition cache for resolving variable definitions.
§Returns

true if one variable is defined as the other plus one, false otherwise.

Source

fn analyze_branch( condition: SsaVarId, cache: &DefinitionCache<T>, ) -> PredicateResult

Analyzes a branch condition variable to determine if it is an opaque predicate.

Follows Copy chains iteratively with a BitSet-based cycle detector to handle SSA copies from phi nodes or obfuscated control flow. At each step:

  1. If the variable has no definition in DefUseIndex but is phi-defined, checks range info for a constant-zero equivalence (all-zero phi = always false).
  2. Delegates to analyze_predicate_with_cache for comparison operations.
  3. For Copy ops, advances to the source variable and continues the loop.
  4. For all other operations, falls back to analyze_branch_op which checks direct truthiness of arithmetic and constant operations.
§Arguments
  • condition - The SSA variable used as the branch condition.
  • cache - Definition cache for resolving the condition’s definition chain.
§Returns

PredicateResult::AlwaysTrue if the branch always takes the true path, AlwaysFalse if it always takes the false path, Unknown if the condition cannot be statically resolved.

Source

fn analyze_branch_op( cond_op: &SsaOp<T>, cache: &DefinitionCache<T>, ) -> PredicateResult

Analyzes a non-Copy, non-comparison operation for direct truthiness in a branch.

Called after the Copy chain has been resolved by analyze_branch. Checks:

  • x ^ x = 0 (always false in brtrue).
  • x - x = 0 (always false).
  • x & 0 or x * 0 = 0 (always false).
  • x | -1 = all-bits-set (always true).
  • Const: zero/null/false is always false; non-zero numeric or true is always true.
§Arguments
  • cond_op - The resolved operation producing the branch condition value.
  • cache - Definition cache for resolving operands of the condition operation.
§Returns

PredicateResult::AlwaysTrue if the operation always produces a non-zero value, AlwaysFalse if it always produces zero, Unknown if the truthiness cannot be determined.

Source

fn analyze_comparison_simplification( op: &SsaOp<T>, cache: &DefinitionCache<T>, ) -> Option<ComparisonSimplification<T>>

Analyzes a comparison operation for algebraic simplification opportunities.

This checks for patterns like:

  • (x - y) == 0x == y
  • (x - y) < 0x < y
  • (x - y) > 0x > y
  • (x ^ y) == 0x == y
  • (cmp) == 1cmp
§Arguments
  • op - The SSA comparison operation to analyze (Ceq, Clt, or Cgt).
  • cache - Definition cache for resolving operand definitions.
§Returns

Some(ComparisonSimplification) if the comparison can be algebraically simplified, None if no simplification applies or the operation is not a comparison.

Source

fn is_zero_var(var: SsaVarId, cache: &DefinitionCache<T>) -> bool

Checks if a variable is defined as a constant zero.

Source

fn is_one_var(var: SsaVarId, cache: &DefinitionCache<T>) -> bool

Checks if a variable is defined as a constant with value 1.

Source

fn analyze_ceq_simplification( dest: SsaVarId, left: SsaVarId, right: SsaVarId, cache: &DefinitionCache<T>, ) -> Option<ComparisonSimplification<T>>

Analyzes a Ceq operation for algebraic simplification.

Detects three patterns:

  • (x - y) == 0 simplifies to x == y (subtraction-zero, skip self-subtraction).
  • (x ^ y) == 0 simplifies to x == y (XOR-zero, skip self-XOR).
  • (cmp) == 1 simplifies to Copy(cmp) when the other operand is a Ceq/Clt/Cgt result, since CIL comparisons already produce 0 or 1.
§Arguments
  • dest - Destination variable of the Ceq (preserved in the simplified op).
  • left - Left operand of the Ceq.
  • right - Right operand of the Ceq.
  • cache - Definition cache for resolving operand definitions.
§Returns

Some(ComparisonSimplification) if a simplification pattern matches, None otherwise.

Source

fn analyze_clt_simplification( dest: SsaVarId, left: SsaVarId, right: SsaVarId, unsigned: bool, cache: &DefinitionCache<T>, ) -> Option<ComparisonSimplification<T>>

Analyzes a Clt operation for algebraic simplification.

Detects (x - y) < 0 and simplifies to x < y (signed only; unsigned subtraction has different overflow semantics). Self-subtraction is skipped since it is handled as a constant predicate (AlwaysFalse).

§Arguments
  • dest - Destination variable of the Clt (preserved in the simplified op).
  • left - Left operand of the Clt.
  • right - Right operand of the Clt.
  • unsigned - Whether this is an unsigned comparison; if true, no simplification is attempted.
  • cache - Definition cache for resolving operand definitions.
§Returns

Some(ComparisonSimplification::SimplerOp) if (x - y) < 0 is detected, None otherwise.

Source

fn analyze_cgt_simplification( dest: SsaVarId, left: SsaVarId, right: SsaVarId, unsigned: bool, cache: &DefinitionCache<T>, ) -> Option<ComparisonSimplification<T>>

Analyzes a Cgt operation for algebraic simplification.

Detects (x - y) > 0 and simplifies to x > y (signed only; unsigned subtraction has different overflow semantics). Self-subtraction is skipped since it is handled as a constant predicate (AlwaysFalse).

§Arguments
  • dest - Destination variable of the Cgt (preserved in the simplified op).
  • left - Left operand of the Cgt.
  • right - Right operand of the Cgt.
  • unsigned - Whether this is an unsigned comparison; if true, no simplification is attempted.
  • cache - Definition cache for resolving operand definitions.
§Returns

Some(ComparisonSimplification::SimplerOp) if (x - y) > 0 is detected, None otherwise.

Source

fn evaluate_with_tracked( ssa: &SsaFunction<T>, condition: SsaVarId, block_idx: usize, ptr_size: PointerSize, ) -> PredicateResult

Fallback evaluator for branch conditions that pattern matching cannot resolve.

Creates an SsaEvaluator and runs a forward pass over all blocks from 0 through block_idx, accumulating concrete values via dataflow propagation. If the condition variable resolves to a constant after evaluation, returns AlwaysTrue (non-zero) or AlwaysFalse (zero).

This catches predicates that require multi-step constant propagation across blocks (e.g., a value computed in block 0 that flows through assignments to block 5’s branch).

§Arguments
  • ssa - The SSA function to evaluate.
  • condition - The branch condition variable to resolve.
  • block_idx - The block containing the branch (evaluation covers blocks 0..=block_idx).
  • ptr_size - Pointer size for the evaluator (affects address arithmetic).
§Returns

PredicateResult::AlwaysTrue or AlwaysFalse if the evaluator resolves the condition to a constant. Unknown if the value depends on runtime inputs, loops, or unresolvable phi operands.

Source

fn analyze_phi_constants( ssa: &SsaFunction<T>, ) -> BTreeMap<SsaVarId, ConstValue<T>>

Detects phi nodes where every operand resolves to the same constant value.

Iterates over all phi nodes in all blocks. For each phi, looks up each operand’s defining operation: if all are Const with identical values, records the mapping from the phi result variable to that constant. These entries are later used to replace the phi with a Const instruction and to resolve branch conditions that depend on phi-defined variables.

§Arguments
  • ssa - The SSA function whose phi nodes are analyzed.
§Returns

A map from phi result variable to the constant value that all operands agree on. Empty if no phi nodes have all-constant, all-identical operands.

Source§

impl<T: Target> OpaquePredicatePass<T>

Source

pub fn run<L>( &self, ssa: &mut SsaFunction<T>, method: &T::MethodRef, events: &L, ptr_size: PointerSize, ) -> bool
where L: EventListener<T> + ?Sized,

Per-method entry point. See run for the free-function shape that host wrappers usually call.

Trait Implementations§

Source§

impl<T: Target> Default for OpaquePredicatePass<T>

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl<T, H> SsaPass<T, H> for OpaquePredicatePass<T>
where T: Target + Send + Sync, T::MethodRef: Send + Sync, H: SsaPassHost<T>,

Source§

fn name(&self) -> &'static str

Returns a unique short name for logging and debugging.
Source§

fn description(&self) -> &'static str

Returns a human-readable description of the pass.
Source§

fn run_on_method( &self, ssa: &mut SsaFunction<T>, method: &T::MethodRef, host: &H, ) -> Result<bool>

Run the pass on a single method’s SSA. Read more
Source§

fn should_run(&self, _method: &T::MethodRef, _host: &H) -> bool

Determines whether this pass should run on a specific method. Read more
Source§

fn run_global(&self, _host: &H) -> Result<bool>

Run the pass on the entire program (interprocedural passes). Read more
Source§

fn is_global(&self) -> bool

Returns whether this pass operates globally (across all methods). Read more
Source§

fn initialize(&mut self, _host: &H) -> Result<()>

Called once before the pass runs in a phase. Read more
Source§

fn finalize(&mut self, _host: &H) -> Result<()>

Called once after the pass completes in a phase. Read more
Source§

fn modification_scope(&self) -> ModificationScope

Declares the extent of modifications this pass makes. Read more
Source§

fn provides(&self) -> &[T::Capability]

Returns the capabilities this pass provides after execution. Read more
Source§

fn requires(&self) -> &[T::Capability]

Returns the capabilities this pass requires before it can run. Read more
Source§

fn reads_peer_ssa(&self) -> bool

Returns whether this pass reads other methods’ SSA during run_on_method. Read more
Source§

fn requires_full_scan(&self) -> bool

Returns whether this pass requires a full scan of all methods every iteration. Read more
Source§

fn fallback_layer(&self) -> usize

Returns the fallback execution layer for this pass. Read more

Auto Trait Implementations§

§

impl<T> Freeze for OpaquePredicatePass<T>

§

impl<T> RefUnwindSafe for OpaquePredicatePass<T>
where T: RefUnwindSafe,

§

impl<T> Send for OpaquePredicatePass<T>
where T: Send,

§

impl<T> Sync for OpaquePredicatePass<T>
where T: Sync,

§

impl<T> Unpin for OpaquePredicatePass<T>
where T: Unpin,

§

impl<T> UnsafeUnpin for OpaquePredicatePass<T>

§

impl<T> UnwindSafe for OpaquePredicatePass<T>
where T: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.