use std::{
collections::HashMap,
sync::{Arc, RwLock},
};
use log::{debug, info, warn};
use crate::{
analysis::{ConstValue, SsaFunction, SsaOp, SsaVarId},
compiler::{CompilerContext, EventKind, ModificationScope, PassCapability, SsaPass},
deobfuscation::{EmulationTemplatePool, ProcessCell},
emulation::{EmValue, EmulationProcess},
metadata::token::Token,
CilObject, Error, Result,
};
pub trait FieldValueExtractor: Send + Sync {
fn extract(
&self,
process: &EmulationProcess,
field_token: Token,
value: &EmValue,
) -> Option<ConstValue>;
fn event_kind(&self) -> EventKind;
fn label(&self) -> &str;
}
pub struct I32Extractor;
impl FieldValueExtractor for I32Extractor {
fn extract(
&self,
_process: &EmulationProcess,
field_token: Token,
value: &EmValue,
) -> Option<ConstValue> {
match value {
EmValue::I32(val) => Some(ConstValue::I32(*val)),
other => {
debug!(
"Int32ValueContainer: field 0x{:08X} has unexpected type {:?}",
field_token.value(),
other
);
None
}
}
}
fn event_kind(&self) -> EventKind {
EventKind::ConstantFolded
}
fn label(&self) -> &str {
"Int32ValueContainer"
}
}
pub struct I64Extractor;
impl FieldValueExtractor for I64Extractor {
fn extract(
&self,
_process: &EmulationProcess,
field_token: Token,
value: &EmValue,
) -> Option<ConstValue> {
match value {
EmValue::I64(val) => Some(ConstValue::I64(*val)),
EmValue::I32(val) => Some(ConstValue::I64(i64::from(*val))),
other => {
debug!(
"Int64ValueContainer: field 0x{:08X} has unexpected type {:?}",
field_token.value(),
other
);
None
}
}
}
fn event_kind(&self) -> EventKind {
EventKind::ConstantFolded
}
fn label(&self) -> &str {
"Int64ValueContainer"
}
}
pub struct F64Extractor;
impl FieldValueExtractor for F64Extractor {
fn extract(
&self,
_process: &EmulationProcess,
field_token: Token,
value: &EmValue,
) -> Option<ConstValue> {
match value {
EmValue::F64(val) => Some(ConstValue::F64(*val)),
EmValue::F32(val) => Some(ConstValue::F64(f64::from(*val))),
other => {
debug!(
"Float64ValueContainer: field 0x{:08X} has unexpected type {:?}",
field_token.value(),
other
);
None
}
}
}
fn event_kind(&self) -> EventKind {
EventKind::ConstantFolded
}
fn label(&self) -> &str {
"Float64ValueContainer"
}
}
pub struct StringExtractor;
impl FieldValueExtractor for StringExtractor {
fn extract(
&self,
process: &EmulationProcess,
field_token: Token,
value: &EmValue,
) -> Option<ConstValue> {
match value {
EmValue::ObjectRef(heap_ref) => match process.address_space().get_string(*heap_ref) {
Ok(s) => Some(ConstValue::DecryptedString(s.to_string())),
Err(e) => {
debug!(
"StringField: field 0x{:08X} has ObjectRef but get_string failed: {}",
field_token.value(),
e
);
None
}
},
EmValue::Null => {
Some(ConstValue::DecryptedString(String::new()))
}
other => {
debug!(
"StringField: field 0x{:08X} has unexpected type {:?}",
field_token.value(),
other
);
None
}
}
}
fn event_kind(&self) -> EventKind {
EventKind::StringDecrypted
}
fn label(&self) -> &str {
"StringField"
}
}
pub struct StaticFieldResolutionPass {
pass_name: &'static str,
pass_description: &'static str,
lazy_process: ProcessCell,
template_pool: Arc<EmulationTemplatePool>,
field_tokens: Vec<Token>,
cctor_token: Option<Token>,
resolved_values: RwLock<HashMap<Token, ConstValue>>,
extractor: Box<dyn FieldValueExtractor>,
capabilities: Vec<PassCapability>,
}
impl StaticFieldResolutionPass {
pub fn new(
pass_name: &'static str,
pass_description: &'static str,
template_pool: Arc<EmulationTemplatePool>,
cctor_token: Option<Token>,
field_tokens: Vec<Token>,
extractor: Box<dyn FieldValueExtractor>,
capabilities: Vec<PassCapability>,
) -> Self {
Self {
pass_name,
pass_description,
lazy_process: ProcessCell::new("static field resolution"),
template_pool,
field_tokens,
cctor_token,
resolved_values: RwLock::new(HashMap::new()),
extractor,
capabilities,
}
}
fn ensure_initialized(&self) -> bool {
let cctor_token = self.cctor_token;
let pool = &self.template_pool;
let result = self.lazy_process.ensure_initialized(
|| {
if let Some(cctor) = cctor_token {
pool.fork_for_targeted_warmup(&[cctor])
} else {
pool.fork().ok()
}
},
|process| {
let mut values = HashMap::new();
for field_token in &self.field_tokens {
match process.get_static(*field_token) {
Ok(Some(ref em_value)) => {
if let Some(const_value) =
self.extractor.extract(process, *field_token, em_value)
{
values.insert(*field_token, const_value);
}
}
Ok(None) => {
debug!(
"{}: field 0x{:08X} not found in emulator statics",
self.extractor.label(),
field_token.value()
);
}
Err(e) => {
debug!(
"{}: error reading field 0x{:08X}: {}",
self.extractor.label(),
field_token.value(),
e
);
}
}
}
if values.is_empty() && !self.field_tokens.is_empty() {
warn!(
"{}: resolved 0/{} field values — container .cctor may have failed or \
field types are unsupported",
self.extractor.label(),
self.field_tokens.len()
);
} else {
info!(
"{}: resolved {}/{} field values via emulation",
self.extractor.label(),
values.len(),
self.field_tokens.len()
);
}
if let Ok(mut guard) = self.resolved_values.write() {
*guard = values;
}
},
);
if result.is_err() {
warn!(
"{}: failed to initialize emulation process",
self.extractor.label()
);
return false;
}
self.resolved_values.read().is_ok_and(|v| !v.is_empty())
}
}
impl SsaPass for StaticFieldResolutionPass {
fn name(&self) -> &'static str {
self.pass_name
}
fn description(&self) -> &'static str {
self.pass_description
}
fn modification_scope(&self) -> ModificationScope {
ModificationScope::InstructionsOnly
}
fn provides(&self) -> &[PassCapability] {
&self.capabilities
}
fn initialize(&mut self, _ctx: &CompilerContext) -> Result<()> {
self.ensure_initialized();
Ok(())
}
fn run_on_method(
&self,
ssa: &mut SsaFunction,
_method_token: Token,
ctx: &CompilerContext,
assembly: &CilObject,
) -> Result<bool> {
if !self.ensure_initialized() {
return Ok(false);
}
let values = self.resolved_values.read().map_err(|e| {
Error::LockError(format!("{} values read: {e}", self.extractor.label()))
})?;
if values.is_empty() {
return Ok(false);
}
let mut replacements: Vec<(usize, usize, SsaVarId, ConstValue)> = Vec::new();
for (block_idx, block) in ssa.blocks().iter().enumerate() {
for (instr_idx, instr) in block.instructions().iter().enumerate() {
if let SsaOp::LoadStaticField { dest, field } = instr.op() {
let field_token = field.token();
let value = values.get(&field_token).or_else(|| {
let resolved = assembly.resolver().resolve_field(field_token)?;
values.get(&resolved)
});
if let Some(value) = value {
replacements.push((block_idx, instr_idx, *dest, value.clone()));
}
}
}
}
if replacements.is_empty() {
return Ok(false);
}
let event_kind = self.extractor.event_kind();
for (block_idx, instr_idx, dest, value) in &replacements {
ssa.replace_instruction_op(
*block_idx,
*instr_idx,
SsaOp::Const {
dest: *dest,
value: value.clone(),
},
);
ctx.events.record(event_kind);
}
Ok(true)
}
fn finalize(&mut self, _ctx: &CompilerContext) -> Result<()> {
self.lazy_process.clear()
}
}