use crate::{
metadata::{
cilassemblyview::CilAssemblyView,
tables::{FieldRvaRaw, TableDataOwned, TableId},
validation::ValidationConfig,
},
CilObject, Error, Result,
};
pub struct WorkingAssembly {
cilobject: Option<CilObject>,
}
impl WorkingAssembly {
#[must_use]
pub fn new(assembly: CilObject) -> Self {
Self {
cilobject: Some(assembly),
}
}
fn assembly(&self) -> Result<&CilObject> {
self.cilobject
.as_ref()
.ok_or_else(|| Error::Other("WorkingAssembly: assembly unavailable".to_string()))
}
pub fn write(&self, offset: usize, data: &[u8]) -> Result<()> {
self.assembly()?.file().write(offset, data)
}
pub fn write_le<T: cowfile::Primitive>(&self, offset: usize, value: T) -> Result<()> {
self.assembly()?.file().write_le(offset, value)
}
pub fn read_le<T: cowfile::Primitive>(&self, offset: usize) -> Result<T> {
self.assembly()?.file().read_le(offset)
}
#[must_use]
pub fn has_pending(&self) -> bool {
self.cilobject
.as_ref()
.is_some_and(|co| co.file().has_pending())
}
pub fn commit(&mut self) -> Result<()> {
if !self.has_pending() {
return Ok(());
}
let co = self
.cilobject
.take()
.ok_or_else(|| Error::Other("WorkingAssembly: assembly unavailable".to_string()))?;
let mut file = co.into_assembly().into_view().into_file()?;
file.commit_pending()?;
let view = CilAssemblyView::from_file_with_validation(file, ValidationConfig::analysis())?;
self.cilobject = Some(CilObject::from_view_with_validation(
view,
ValidationConfig::analysis(),
)?);
Ok(())
}
pub fn replace_assembly(&mut self, assembly: CilObject) {
self.cilobject = Some(assembly);
}
pub fn store_field_data(&mut self, entries: Vec<(u32, Vec<u8>)>) -> Result<()> {
if entries.is_empty() {
return Ok(());
}
let co = self
.cilobject
.take()
.ok_or_else(|| Error::Other("WorkingAssembly: assembly unavailable".into()))?;
let mut cil = co.into_assembly();
for (fieldrva_rid, data) in entries {
let placeholder_rva = cil.store_field_data(data);
let existing_row = cil
.view()
.tables()
.and_then(|t| t.table::<FieldRvaRaw>())
.and_then(|table| table.get(fieldrva_rid))
.ok_or_else(|| Error::Other(format!("FieldRVA row {fieldrva_rid} not found")))?;
let updated_row = FieldRvaRaw {
rid: existing_row.rid,
token: existing_row.token,
offset: existing_row.offset,
rva: placeholder_rva,
field: existing_row.field,
};
cil.table_row_update(
TableId::FieldRVA,
fieldrva_rid,
TableDataOwned::FieldRVA(updated_row),
)?;
}
let new_co = cil.into_cilobject_with(ValidationConfig::analysis(), Default::default())?;
self.cilobject = Some(new_co);
Ok(())
}
pub fn cilobject(&self) -> Result<&CilObject> {
self.assembly()
}
pub fn into_cilobject(mut self) -> Result<CilObject> {
self.commit()?;
self.cilobject
.ok_or_else(|| Error::Other("WorkingAssembly: assembly unavailable".to_string()))
}
}
#[cfg(test)]
mod tests {
use crate::{
deobfuscation::techniques::WorkingAssembly, metadata::validation::ValidationConfig,
CilObject,
};
fn load_sample() -> CilObject {
let p = format!(
"{}/tests/samples/packers/confuserex/1.6.0/original.exe",
env!("CARGO_MANIFEST_DIR")
);
CilObject::from_path_with_validation(&p, ValidationConfig::analysis())
.unwrap_or_else(|e| panic!("Failed to load test sample: {e}"))
}
#[test]
fn test_new_no_pending() {
let wa = WorkingAssembly::new(load_sample());
assert!(!wa.has_pending());
}
#[test]
fn test_cilobject_accessible() {
let wa = WorkingAssembly::new(load_sample());
assert!(wa.cilobject().is_ok());
assert!(wa.cilobject().unwrap().module().is_some());
}
#[test]
fn test_write_creates_pending() {
let wa = WorkingAssembly::new(load_sample());
wa.write(0, &[0x4D]).unwrap();
assert!(wa.has_pending());
}
#[test]
fn test_commit_clears_pending() {
let mut wa = WorkingAssembly::new(load_sample());
wa.write(0, &[0x4D]).unwrap();
assert!(wa.has_pending());
wa.commit().unwrap();
assert!(!wa.has_pending());
}
#[test]
fn test_commit_noop_without_pending() {
let mut wa = WorkingAssembly::new(load_sample());
wa.commit().unwrap();
assert!(!wa.has_pending());
}
#[test]
fn test_replace_assembly() {
let mut wa = WorkingAssembly::new(load_sample());
wa.write(0, &[0x4D]).unwrap();
assert!(wa.has_pending());
wa.replace_assembly(load_sample());
assert!(!wa.has_pending());
assert!(wa.cilobject().is_ok());
}
#[test]
fn test_into_cilobject() {
let co = WorkingAssembly::new(load_sample())
.into_cilobject()
.unwrap();
assert!(co.module().is_some());
}
}