use std::{collections::HashSet, sync::Arc};
use crate::{
analysis::{ConstValue, SsaFunction, SsaOp, SsaVarId},
compiler::CompilerContext,
metadata::token::Token,
utils::graph::{algorithms::DominatorTree, NodeId},
CilObject,
};
#[derive(Debug, Clone)]
pub struct StateUpdateCall {
pub block_idx: usize,
pub instr_idx: usize,
pub dest: SsaVarId,
pub flag_var: SsaVarId,
pub increment_var: SsaVarId,
}
#[derive(Debug, Clone)]
pub struct StateMachineCallSite {
pub block_idx: usize,
pub instr_idx: usize,
pub dest: SsaVarId,
pub decryptor: Token,
pub call_target: Token,
pub state_var: SsaVarId,
pub encoded_var: SsaVarId,
pub feeding_update_idx: usize,
}
impl StateMachineCallSite {
#[must_use]
pub fn location(&self) -> usize {
self.block_idx * 1000 + self.instr_idx
}
}
pub struct CfgInfo<'a> {
pub dom_tree: &'a DominatorTree,
pub predecessors: &'a [Vec<usize>],
pub node_count: usize,
pub entry: NodeId,
}
pub trait StateMachineProvider: Send + Sync + std::fmt::Debug {
fn name(&self) -> &'static str;
fn semantics(&self) -> &StateMachineSemantics;
fn applies_to_method(&self, method: Token) -> bool;
fn methods(&self) -> Vec<Token>;
fn find_initializations(
&self,
ssa: &SsaFunction,
ctx: &CompilerContext,
method: Token,
assembly: &Arc<CilObject>,
) -> Vec<(usize, usize, u32)>;
fn find_state_updates(&self, ssa: &SsaFunction) -> Vec<StateUpdateCall>;
fn trace_to_constant(
&self,
var: SsaVarId,
ssa: &SsaFunction,
ctx: &CompilerContext,
method: Token,
) -> Option<ConstValue> {
if let Some(val) = ctx.with_known_value(method, var, Clone::clone) {
return Some(val);
}
if let Some(SsaOp::Const { value, .. }) = ssa.get_definition(var) {
return Some(value.clone());
}
None
}
fn compute_key(&self, state_value: u64, encoded: i32) -> i32 {
#[allow(clippy::cast_possible_truncation)]
let state_i32 = state_value as i32;
state_i32 ^ encoded
}
fn find_decryptor_call_sites(
&self,
_ssa: &SsaFunction,
_state_updates: &[StateUpdateCall],
_decryptor_tokens: &HashSet<Token>,
_assembly: &Arc<CilObject>,
) -> Vec<StateMachineCallSite> {
Vec::new()
}
fn collect_updates_for_call(
&self,
_call_site: &StateMachineCallSite,
_all_updates: &[StateUpdateCall],
_cfg_info: &CfgInfo<'_>,
) -> Vec<usize> {
Vec::new()
}
fn find_seed_for_call(
&self,
seeds: &[(usize, usize, u32)],
call_site: &StateMachineCallSite,
cfg_info: &CfgInfo<'_>,
) -> Option<u32> {
if seeds.is_empty() {
return None;
}
if seeds.len() == 1 {
return Some(seeds[0].2);
}
let mut best_seed: Option<(usize, usize, u32)> = None;
for &(seed_block, seed_instr, seed_val) in seeds {
if seed_block >= cfg_info.node_count || call_site.block_idx >= cfg_info.node_count {
continue;
}
if seed_block == call_site.block_idx && seed_instr < call_site.instr_idx {
if best_seed.is_none()
|| best_seed.is_some_and(|(b, i, _)| b != seed_block || i < seed_instr)
{
best_seed = Some((seed_block, seed_instr, seed_val));
}
} else if cfg_info
.dom_tree
.dominates(cfg_info.entry, NodeId::new(seed_block))
&& cfg_info
.dom_tree
.strictly_dominates(NodeId::new(seed_block), NodeId::new(call_site.block_idx))
{
if best_seed.is_none() {
best_seed = Some((seed_block, seed_instr, seed_val));
} else if let Some((best_block, _, _)) = best_seed {
if best_block != call_site.block_idx {
let best_depth = cfg_info.dom_tree.depth(NodeId::new(best_block));
let seed_depth = cfg_info.dom_tree.depth(NodeId::new(seed_block));
if seed_depth > best_depth {
best_seed = Some((seed_block, seed_instr, seed_val));
}
}
}
}
}
best_seed
.map(|(_, _, v)| v)
.or_else(|| seeds.first().map(|(_, _, v)| *v))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SsaOpKind {
Xor,
Add,
Sub,
Mul,
And,
Or,
Shl,
Shr,
ShrA,
Rol,
Ror,
Not,
Neg,
}
impl std::fmt::Display for SsaOpKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Xor => write!(f, "xor"),
Self::Add => write!(f, "add"),
Self::Sub => write!(f, "sub"),
Self::Mul => write!(f, "mul"),
Self::And => write!(f, "and"),
Self::Or => write!(f, "or"),
Self::Shl => write!(f, "shl"),
Self::Shr => write!(f, "shr"),
Self::ShrA => write!(f, "shr.a"),
Self::Rol => write!(f, "rol"),
Self::Ror => write!(f, "ror"),
Self::Not => write!(f, "not"),
Self::Neg => write!(f, "neg"),
}
}
}
#[derive(Debug, Clone)]
pub struct StateSlotOperation {
pub op: SsaOpKind,
pub reversed: bool,
}
impl StateSlotOperation {
#[must_use]
pub fn new(op: SsaOpKind) -> Self {
Self {
op,
reversed: false,
}
}
#[must_use]
pub fn new_reversed(op: SsaOpKind) -> Self {
Self { op, reversed: true }
}
#[must_use]
pub fn xor() -> Self {
Self::new(SsaOpKind::Xor)
}
#[must_use]
pub fn add() -> Self {
Self::new(SsaOpKind::Add)
}
#[must_use]
pub fn sub() -> Self {
Self::new(SsaOpKind::Sub)
}
#[must_use]
pub fn mul() -> Self {
Self::new(SsaOpKind::Mul)
}
#[must_use]
pub fn and() -> Self {
Self::new(SsaOpKind::And)
}
#[must_use]
pub fn or() -> Self {
Self::new(SsaOpKind::Or)
}
#[must_use]
pub fn apply(&self, a: u64, b: u64) -> u64 {
let (left, right) = if self.reversed { (b, a) } else { (a, b) };
match self.op {
SsaOpKind::Xor => left ^ right,
SsaOpKind::Add => left.wrapping_add(right),
SsaOpKind::Sub => left.wrapping_sub(right),
SsaOpKind::Mul => left.wrapping_mul(right),
SsaOpKind::And => left & right,
SsaOpKind::Or => left | right,
SsaOpKind::Shl => left << (right & 63),
SsaOpKind::Shr => left >> (right & 63),
SsaOpKind::ShrA => (left.cast_signed() >> (right & 63)).cast_unsigned(),
SsaOpKind::Rol => left.rotate_left((right & 63) as u32),
SsaOpKind::Ror => left.rotate_right((right & 63) as u32),
SsaOpKind::Not => !left,
SsaOpKind::Neg => (-left.cast_signed()).cast_unsigned(),
}
}
#[must_use]
pub fn apply_u32(&self, a: u32, b: u32) -> u32 {
let (left, right) = if self.reversed { (b, a) } else { (a, b) };
match self.op {
SsaOpKind::Xor => left ^ right,
SsaOpKind::Add => left.wrapping_add(right),
SsaOpKind::Sub => left.wrapping_sub(right),
SsaOpKind::Mul => left.wrapping_mul(right),
SsaOpKind::And => left & right,
SsaOpKind::Or => left | right,
SsaOpKind::Shl => left << (right & 31),
SsaOpKind::Shr => left >> (right & 31),
SsaOpKind::ShrA => (left.cast_signed() >> (right & 31)).cast_unsigned(),
SsaOpKind::Rol => left.rotate_left(right & 31),
SsaOpKind::Ror => left.rotate_right(right & 31),
SsaOpKind::Not => !left,
SsaOpKind::Neg => (-left.cast_signed()).cast_unsigned(),
}
}
}
#[derive(Debug, Clone)]
pub struct StateMachineSemantics {
pub type_token: Option<Token>,
pub init_method: Option<Token>,
pub update_method: Option<Token>,
pub slot_count: usize,
pub slot_ops: Vec<StateSlotOperation>,
pub init_ops: Vec<StateSlotOperation>,
pub init_constant: Option<u64>,
pub explicit_flag_bit: u8,
pub update_slot_mask: u8,
pub get_slot_mask: u8,
pub get_slot_shift: u8,
}
impl Default for StateMachineSemantics {
fn default() -> Self {
Self {
type_token: None,
init_method: None,
update_method: None,
slot_count: 4,
slot_ops: Vec::new(),
init_ops: Vec::new(),
init_constant: None,
explicit_flag_bit: 7,
update_slot_mask: 0x03,
get_slot_mask: 0x03,
get_slot_shift: 2,
}
}
}
impl StateMachineSemantics {
#[must_use]
pub fn confuserex_cfgctx(multiplier: u32) -> Self {
Self {
type_token: None,
init_method: None,
update_method: None,
slot_count: 4,
slot_ops: vec![
StateSlotOperation::xor(), StateSlotOperation::add(), StateSlotOperation::xor(), StateSlotOperation::sub(), ],
init_ops: vec![
StateSlotOperation::mul(), StateSlotOperation::mul(), StateSlotOperation::mul(), StateSlotOperation::mul(), ],
init_constant: Some(u64::from(multiplier)),
explicit_flag_bit: 7,
update_slot_mask: 0x03,
get_slot_mask: 0x03,
get_slot_shift: 2,
}
}
#[must_use]
pub fn confuserex_default() -> Self {
Self::confuserex_cfgctx(0x2141_2321)
}
#[must_use]
pub fn slot_operation(&self, slot: usize) -> Option<&StateSlotOperation> {
self.slot_ops.get(slot)
}
#[must_use]
pub fn init_operation(&self, slot: usize) -> Option<&StateSlotOperation> {
if self.init_ops.is_empty() {
None
} else {
Some(&self.init_ops[slot % self.init_ops.len()])
}
}
}
#[derive(Debug, Clone)]
pub struct StateMachineState {
pub slots: Vec<u64>,
semantics: Arc<StateMachineSemantics>,
}
impl StateMachineState {
#[must_use]
pub fn new(semantics: Arc<StateMachineSemantics>) -> Self {
let slots = vec![0; semantics.slot_count];
Self { slots, semantics }
}
#[must_use]
pub fn from_seed(seed: u64, semantics: Arc<StateMachineSemantics>) -> Self {
let mut slots = Vec::with_capacity(semantics.slot_count);
let mut current = seed;
for i in 0..semantics.slot_count {
if let (Some(op), Some(constant)) =
(semantics.init_operation(i), semantics.init_constant)
{
current = op.apply(current, constant);
}
slots.push(current);
}
Self { slots, semantics }
}
#[must_use]
pub fn from_seed_u32(seed: u32, semantics: Arc<StateMachineSemantics>) -> Self {
let mut slots = Vec::with_capacity(semantics.slot_count);
let mut current = seed;
for i in 0..semantics.slot_count {
if let (Some(op), Some(constant)) =
(semantics.init_operation(i), semantics.init_constant)
{
#[allow(clippy::cast_possible_truncation)]
let const_u32 = constant as u32;
current = op.apply_u32(current, const_u32);
}
slots.push(u64::from(current));
}
Self { slots, semantics }
}
#[must_use]
pub fn next(&mut self, flag: u8, value: u64) -> u64 {
let update_slot = (flag & self.semantics.update_slot_mask) as usize;
let get_slot =
((flag >> self.semantics.get_slot_shift) & self.semantics.get_slot_mask) as usize;
let is_explicit = (flag & (1 << self.semantics.explicit_flag_bit)) != 0;
let update_slot = update_slot % self.slots.len().max(1);
let get_slot = get_slot % self.slots.len().max(1);
if is_explicit {
self.slots[update_slot] = value;
} else if let Some(op) = self.semantics.slot_operation(update_slot) {
self.slots[update_slot] = op.apply(self.slots[update_slot], value);
}
self.slots[get_slot]
}
#[must_use]
pub fn next_u32(&mut self, flag: u8, value: u32) -> u32 {
let update_slot = (flag & self.semantics.update_slot_mask) as usize;
let get_slot =
((flag >> self.semantics.get_slot_shift) & self.semantics.get_slot_mask) as usize;
let is_explicit = (flag & (1 << self.semantics.explicit_flag_bit)) != 0;
let update_slot = update_slot % self.slots.len().max(1);
let get_slot = get_slot % self.slots.len().max(1);
if is_explicit {
self.slots[update_slot] = u64::from(value);
} else if let Some(op) = self.semantics.slot_operation(update_slot) {
#[allow(clippy::cast_possible_truncation)]
let current = self.slots[update_slot] as u32;
self.slots[update_slot] = u64::from(op.apply_u32(current, value));
}
#[allow(clippy::cast_possible_truncation)]
let result = self.slots[get_slot] as u32;
result
}
#[must_use]
pub fn get(&self, slot: usize) -> u64 {
self.slots.get(slot).copied().unwrap_or(0)
}
#[must_use]
pub fn get_u32(&self, slot: usize) -> u32 {
#[allow(clippy::cast_possible_truncation)]
self.slots.get(slot).map_or(0, |&v| v as u32)
}
pub fn set(&mut self, slot: usize, value: u64) {
if slot < self.slots.len() {
self.slots[slot] = value;
}
}
#[must_use]
pub fn slot_count(&self) -> usize {
self.slots.len()
}
#[must_use]
pub fn semantics(&self) -> &StateMachineSemantics {
&self.semantics
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_slot_operation_xor() {
let op = StateSlotOperation::xor();
assert_eq!(op.apply(0x1234, 0x5678), 0x1234 ^ 0x5678);
assert_eq!(op.apply_u32(0x1234, 0x5678), 0x1234 ^ 0x5678);
}
#[test]
fn test_slot_operation_add() {
let op = StateSlotOperation::add();
assert_eq!(op.apply(100, 50), 150);
assert_eq!(op.apply_u32(u32::MAX, 1), 0); }
#[test]
fn test_slot_operation_sub() {
let op = StateSlotOperation::sub();
assert_eq!(op.apply(100, 30), 70);
assert_eq!(op.apply_u32(0, 1), u32::MAX); }
#[test]
fn test_slot_operation_mul() {
let op = StateSlotOperation::mul();
assert_eq!(op.apply(7, 6), 42);
assert_eq!(
op.apply_u32(0x1234_5678, 0x2141_2321),
0x1234_5678_u32.wrapping_mul(0x2141_2321)
);
}
#[test]
fn test_slot_operation_reversed() {
let op = StateSlotOperation::new_reversed(SsaOpKind::Sub);
assert_eq!(op.apply(30, 100), 70); }
#[test]
fn test_confuserex_semantics() {
let semantics = StateMachineSemantics::confuserex_default();
assert_eq!(semantics.slot_count, 4);
assert_eq!(semantics.init_constant, Some(0x2141_2321));
assert_eq!(semantics.slot_ops.len(), 4);
assert_eq!(semantics.init_ops.len(), 4);
assert_eq!(semantics.explicit_flag_bit, 7);
assert_eq!(semantics.update_slot_mask, 0x03);
assert_eq!(semantics.get_slot_mask, 0x03);
assert_eq!(semantics.get_slot_shift, 2);
}
#[test]
fn test_state_from_seed() {
let semantics = Arc::new(StateMachineSemantics::confuserex_default());
let state = StateMachineState::from_seed_u32(0x1234_5678, semantics);
let mut seed = 0x1234_5678_u32;
seed = seed.wrapping_mul(0x2141_2321);
assert_eq!(state.get_u32(0), seed);
seed = seed.wrapping_mul(0x2141_2321);
assert_eq!(state.get_u32(1), seed);
seed = seed.wrapping_mul(0x2141_2321);
assert_eq!(state.get_u32(2), seed);
seed = seed.wrapping_mul(0x2141_2321);
assert_eq!(state.get_u32(3), seed);
}
#[test]
fn test_state_next_incremental() {
let semantics = Arc::new(StateMachineSemantics::confuserex_default());
let mut state = StateMachineState::new(Arc::clone(&semantics));
state.set(0, 0x1000_0000);
state.set(1, 0x2000_0000);
state.set(2, 0x3000_0000);
state.set(3, 0x4000_0000);
let flag = 0b0000_0100;
let result = state.next_u32(flag, 0x0000_1111);
assert_eq!(state.get_u32(0), 0x1000_0000 ^ 0x0000_1111);
assert_eq!(result, state.get_u32(1));
let flag = 0b0000_1001;
let result = state.next_u32(flag, 0x0000_2222);
assert_eq!(state.get_u32(1), 0x2000_0000_u32.wrapping_add(0x0000_2222));
assert_eq!(result, state.get_u32(2));
}
#[test]
fn test_state_next_explicit() {
let semantics = Arc::new(StateMachineSemantics::confuserex_default());
let mut state = StateMachineState::new(Arc::clone(&semantics));
state.set(0, 0x1000_0000);
let flag = 0x80;
let result = state.next_u32(flag, 0xDEAD_BEEF);
assert_eq!(state.get_u32(0), 0xDEAD_BEEF);
assert_eq!(result, 0xDEAD_BEEF);
}
#[test]
fn test_state_slot_count() {
let semantics = Arc::new(StateMachineSemantics::confuserex_default());
let state = StateMachineState::new(semantics);
assert_eq!(state.slot_count(), 4);
}
#[test]
fn test_custom_semantics() {
let semantics = Arc::new(StateMachineSemantics {
slot_count: 2,
slot_ops: vec![StateSlotOperation::add(), StateSlotOperation::xor()],
init_ops: vec![StateSlotOperation::mul()],
init_constant: Some(0x1337),
..Default::default()
});
let state = StateMachineState::from_seed_u32(1, Arc::clone(&semantics));
assert_eq!(state.slot_count(), 2);
assert_eq!(state.get_u32(0), 0x1337);
assert_eq!(state.get_u32(1), 0x1337_u32.wrapping_mul(0x1337));
}
}